Upgrade Playlist Features
This commit is contained in:
1638
main/CMakeLists.txt
1638
main/CMakeLists.txt
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
671
main/alarm_manager.cc
Normal file
671
main/alarm_manager.cc
Normal file
@@ -0,0 +1,671 @@
|
||||
#include "alarm_manager.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <cJSON.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#define TAG "AlarmManager"
|
||||
#define ALARM_SETTINGS_NAMESPACE "alarms"
|
||||
|
||||
AlarmManager::AlarmManager()
|
||||
: initialized_(false), next_alarm_id_(1),
|
||||
default_snooze_minutes_(5), default_max_snooze_count_(3) {
|
||||
}
|
||||
|
||||
AlarmManager::~AlarmManager() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void AlarmManager::Initialize() {
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing Alarm Manager");
|
||||
|
||||
// 初始化设置存储
|
||||
settings_ = std::make_unique<Settings>(ALARM_SETTINGS_NAMESPACE, true);
|
||||
|
||||
// 从存储中加载闹钟
|
||||
LoadAlarmsFromStorage();
|
||||
|
||||
// 获取下一个闹钟ID
|
||||
next_alarm_id_ = settings_->GetInt("next_id", 1);
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "Alarm Manager initialized with %d alarms", alarms_.size());
|
||||
}
|
||||
|
||||
void AlarmManager::Cleanup() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Cleaning up Alarm Manager");
|
||||
|
||||
// 停止所有活动的闹钟
|
||||
StopAllActiveAlarms();
|
||||
|
||||
// 保存数据
|
||||
SaveAlarmsToStorage();
|
||||
|
||||
// 清理资源
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
alarms_.clear();
|
||||
}
|
||||
|
||||
settings_.reset();
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
int AlarmManager::AddAlarm(int hour, int minute, AlarmRepeatMode repeat_mode,
|
||||
const std::string& label, const std::string& music_name) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "AlarmManager not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||
ESP_LOGE(TAG, "Invalid time: %02d:%02d", hour, minute);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
auto alarm = std::make_unique<AlarmItem>();
|
||||
alarm->id = GetNextAlarmId();
|
||||
alarm->hour = hour;
|
||||
alarm->minute = minute;
|
||||
alarm->repeat_mode = repeat_mode;
|
||||
alarm->label = label;
|
||||
alarm->music_name = music_name;
|
||||
alarm->status = kAlarmEnabled;
|
||||
alarm->snooze_minutes = default_snooze_minutes_;
|
||||
alarm->max_snooze_count = default_max_snooze_count_;
|
||||
|
||||
// 设置星期掩码
|
||||
switch (repeat_mode) {
|
||||
case kAlarmDaily:
|
||||
alarm->weekdays_mask = 0b1111111; // 每天
|
||||
break;
|
||||
case kAlarmWeekdays:
|
||||
alarm->weekdays_mask = 0b0111110; // 周一到周五
|
||||
break;
|
||||
case kAlarmWeekends:
|
||||
alarm->weekdays_mask = 0b1000001; // 周六周日
|
||||
break;
|
||||
default:
|
||||
alarm->weekdays_mask = 0; // 一次性或自定义
|
||||
break;
|
||||
}
|
||||
|
||||
int alarm_id = alarm->id;
|
||||
alarms_.push_back(std::move(alarm));
|
||||
|
||||
// 保存到存储
|
||||
SaveAlarmToStorage(*alarms_.back());
|
||||
settings_->SetInt("next_id", next_alarm_id_);
|
||||
|
||||
ESP_LOGI(TAG, "Added alarm %d: %02d:%02d, repeat=%d, label='%s', music='%s'",
|
||||
alarm_id, hour, minute, repeat_mode, label.c_str(), music_name.c_str());
|
||||
|
||||
return alarm_id;
|
||||
}
|
||||
|
||||
bool AlarmManager::RemoveAlarm(int alarm_id) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
auto it = std::find_if(alarms_.begin(), alarms_.end(),
|
||||
[alarm_id](const auto& alarm) { return alarm->id == alarm_id; });
|
||||
|
||||
if (it != alarms_.end()) {
|
||||
ESP_LOGI(TAG, "Removing alarm %d", alarm_id);
|
||||
alarms_.erase(it);
|
||||
RemoveAlarmFromStorage(alarm_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Alarm %d not found for removal", alarm_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AlarmManager::EnableAlarm(int alarm_id, bool enabled) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
auto it = std::find_if(alarms_.begin(), alarms_.end(),
|
||||
[alarm_id](const auto& alarm) { return alarm->id == alarm_id; });
|
||||
|
||||
if (it != alarms_.end()) {
|
||||
(*it)->status = enabled ? kAlarmEnabled : kAlarmDisabled;
|
||||
SaveAlarmToStorage(**it);
|
||||
ESP_LOGI(TAG, "Alarm %d %s", alarm_id, enabled ? "enabled" : "disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AlarmManager::ModifyAlarm(int alarm_id, int hour, int minute, AlarmRepeatMode repeat_mode,
|
||||
const std::string& label, const std::string& music_name) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||
ESP_LOGE(TAG, "Invalid time: %02d:%02d", hour, minute);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
auto it = std::find_if(alarms_.begin(), alarms_.end(),
|
||||
[alarm_id](const auto& alarm) { return alarm->id == alarm_id; });
|
||||
|
||||
if (it != alarms_.end()) {
|
||||
(*it)->hour = hour;
|
||||
(*it)->minute = minute;
|
||||
(*it)->repeat_mode = repeat_mode;
|
||||
(*it)->label = label;
|
||||
(*it)->music_name = music_name;
|
||||
|
||||
// 重新设置星期掩码
|
||||
switch (repeat_mode) {
|
||||
case kAlarmDaily:
|
||||
(*it)->weekdays_mask = 0b1111111;
|
||||
break;
|
||||
case kAlarmWeekdays:
|
||||
(*it)->weekdays_mask = 0b0111110;
|
||||
break;
|
||||
case kAlarmWeekends:
|
||||
(*it)->weekdays_mask = 0b1000001;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SaveAlarmToStorage(**it);
|
||||
ESP_LOGI(TAG, "Modified alarm %d: %02d:%02d", alarm_id, hour, minute);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<AlarmItem> AlarmManager::GetAllAlarms() const {
|
||||
if (!initialized_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
std::vector<AlarmItem> result;
|
||||
|
||||
for (const auto& alarm : alarms_) {
|
||||
result.push_back(*alarm);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AlarmItem* AlarmManager::GetAlarm(int alarm_id) {
|
||||
if (!initialized_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
auto it = std::find_if(alarms_.begin(), alarms_.end(),
|
||||
[alarm_id](const auto& alarm) { return alarm->id == alarm_id; });
|
||||
|
||||
return (it != alarms_.end()) ? it->get() : nullptr;
|
||||
}
|
||||
|
||||
std::vector<AlarmItem> AlarmManager::GetActiveAlarms() const {
|
||||
if (!initialized_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
std::vector<AlarmItem> result;
|
||||
|
||||
for (const auto& alarm : alarms_) {
|
||||
if (alarm->status == kAlarmTriggered || alarm->status == kAlarmSnoozed) {
|
||||
result.push_back(*alarm);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string AlarmManager::GetNextAlarmInfo() const {
|
||||
if (!initialized_) {
|
||||
return "闹钟管理器未初始化";
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
// 查找下一个要触发的闹钟
|
||||
AlarmItem* next_alarm = nullptr;
|
||||
int64_t current_time = GetCurrentTimeInMinutes();
|
||||
int current_weekday = GetCurrentWeekday();
|
||||
int64_t min_time_diff = 24 * 60 * 7; // 一周的分钟数
|
||||
|
||||
for (const auto& alarm : alarms_) {
|
||||
if (alarm->status != kAlarmEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算今天和明天的时间差
|
||||
int64_t alarm_time = alarm->hour * 60 + alarm->minute;
|
||||
|
||||
for (int day_offset = 0; day_offset < 7; day_offset++) {
|
||||
int check_weekday = (current_weekday + day_offset) % 7;
|
||||
|
||||
if (day_offset == 0 && alarm_time <= current_time) {
|
||||
continue; // 今天已经过了
|
||||
}
|
||||
|
||||
if (alarm->repeat_mode == kAlarmOnce && day_offset > 0) {
|
||||
continue; // 一次性闹钟只检查今天
|
||||
}
|
||||
|
||||
if (IsWeekdayActive(*alarm, check_weekday)) {
|
||||
int64_t time_diff = day_offset * 24 * 60 + alarm_time - current_time;
|
||||
if (day_offset == 0) {
|
||||
time_diff = alarm_time - current_time;
|
||||
}
|
||||
|
||||
if (time_diff < min_time_diff) {
|
||||
min_time_diff = time_diff;
|
||||
next_alarm = alarm.get();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!next_alarm) {
|
||||
return "无活动闹钟";
|
||||
}
|
||||
|
||||
// 格式化下一个闹钟信息
|
||||
std::ostringstream oss;
|
||||
oss << "下个闹钟: " << FormatTime(next_alarm->hour, next_alarm->minute);
|
||||
|
||||
if (min_time_diff < 24 * 60) {
|
||||
int hours = min_time_diff / 60;
|
||||
int minutes = min_time_diff % 60;
|
||||
if (hours > 0) {
|
||||
oss << " (" << hours << "小时" << minutes << "分钟后)";
|
||||
} else {
|
||||
oss << " (" << minutes << "分钟后)";
|
||||
}
|
||||
} else {
|
||||
int days = min_time_diff / (24 * 60);
|
||||
oss << " (" << days << "天后)";
|
||||
}
|
||||
|
||||
if (!next_alarm->label.empty()) {
|
||||
oss << " - " << next_alarm->label;
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool AlarmManager::SnoozeAlarm(int alarm_id) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
auto it = std::find_if(alarms_.begin(), alarms_.end(),
|
||||
[alarm_id](const auto& alarm) { return alarm->id == alarm_id; });
|
||||
|
||||
if (it != alarms_.end() && (*it)->status == kAlarmTriggered) {
|
||||
if ((*it)->snooze_count < (*it)->max_snooze_count) {
|
||||
(*it)->status = kAlarmSnoozed;
|
||||
(*it)->snooze_count++;
|
||||
(*it)->next_snooze_time = esp_timer_get_time() / 1000000 + (*it)->snooze_minutes * 60;
|
||||
|
||||
ESP_LOGI(TAG, "Snoozed alarm %d for %d minutes (count: %d/%d)",
|
||||
alarm_id, (*it)->snooze_minutes, (*it)->snooze_count, (*it)->max_snooze_count);
|
||||
|
||||
if (on_alarm_snoozed_) {
|
||||
on_alarm_snoozed_(**it);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Alarm %d exceeded max snooze count, stopping", alarm_id);
|
||||
StopAlarm(alarm_id);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AlarmManager::StopAlarm(int alarm_id) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
auto it = std::find_if(alarms_.begin(), alarms_.end(),
|
||||
[alarm_id](const auto& alarm) { return alarm->id == alarm_id; });
|
||||
|
||||
if (it != alarms_.end() &&
|
||||
((*it)->status == kAlarmTriggered || (*it)->status == kAlarmSnoozed)) {
|
||||
|
||||
AlarmStatus old_status = (*it)->status;
|
||||
(*it)->status = ((*it)->repeat_mode == kAlarmOnce) ? kAlarmDisabled : kAlarmEnabled;
|
||||
(*it)->snooze_count = 0;
|
||||
(*it)->next_snooze_time = 0;
|
||||
|
||||
ESP_LOGI(TAG, "Stopped alarm %d", alarm_id);
|
||||
|
||||
if (on_alarm_stopped_) {
|
||||
on_alarm_stopped_(**it);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AlarmManager::StopAllActiveAlarms() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
for (auto& alarm : alarms_) {
|
||||
if (alarm->status == kAlarmTriggered || alarm->status == kAlarmSnoozed) {
|
||||
alarm->status = (alarm->repeat_mode == kAlarmOnce) ? kAlarmDisabled : kAlarmEnabled;
|
||||
alarm->snooze_count = 0;
|
||||
alarm->next_snooze_time = 0;
|
||||
|
||||
if (on_alarm_stopped_) {
|
||||
on_alarm_stopped_(*alarm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Stopped all active alarms");
|
||||
}
|
||||
|
||||
void AlarmManager::CheckAlarms() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t current_time_seconds = esp_timer_get_time() / 1000000;
|
||||
int64_t current_time_minutes = GetCurrentTimeInMinutes();
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
for (auto& alarm : alarms_) {
|
||||
// 检查贪睡闹钟
|
||||
if (alarm->status == kAlarmSnoozed &&
|
||||
current_time_seconds >= alarm->next_snooze_time) {
|
||||
|
||||
alarm->status = kAlarmTriggered;
|
||||
alarm->next_snooze_time = 0;
|
||||
|
||||
ESP_LOGI(TAG, "Snooze ended for alarm %d, triggering again", alarm->id);
|
||||
|
||||
if (on_alarm_triggered_) {
|
||||
on_alarm_triggered_(*alarm);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查正常闹钟触发
|
||||
if (alarm->status != kAlarmEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t alarm_time_minutes = alarm->hour * 60 + alarm->minute;
|
||||
|
||||
// 检查是否是触发时间(精确到分钟)
|
||||
if (alarm_time_minutes != current_time_minutes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否应该在今天触发
|
||||
if (!ShouldTriggerToday(*alarm)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 防止重复触发(同一分钟内)
|
||||
int64_t current_time_in_seconds = esp_timer_get_time() / 1000000;
|
||||
if (alarm->last_triggered_time > 0 &&
|
||||
(current_time_in_seconds - alarm->last_triggered_time) < 60) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 触发闹钟
|
||||
alarm->status = kAlarmTriggered;
|
||||
alarm->last_triggered_time = current_time_in_seconds;
|
||||
alarm->snooze_count = 0;
|
||||
|
||||
ESP_LOGI(TAG, "Triggering alarm %d: %02d:%02d - %s",
|
||||
alarm->id, alarm->hour, alarm->minute, alarm->label.c_str());
|
||||
|
||||
if (on_alarm_triggered_) {
|
||||
on_alarm_triggered_(*alarm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmManager::SetAlarmTriggeredCallback(AlarmTriggeredCallback callback) {
|
||||
on_alarm_triggered_ = callback;
|
||||
}
|
||||
|
||||
void AlarmManager::SetAlarmSnoozeCallback(AlarmSnoozeCallback callback) {
|
||||
on_alarm_snoozed_ = callback;
|
||||
}
|
||||
|
||||
void AlarmManager::SetAlarmStopCallback(AlarmStopCallback callback) {
|
||||
on_alarm_stopped_ = callback;
|
||||
}
|
||||
|
||||
void AlarmManager::SetDefaultSnoozeMinutes(int minutes) {
|
||||
default_snooze_minutes_ = std::max(1, std::min(60, minutes));
|
||||
}
|
||||
|
||||
void AlarmManager::SetDefaultMaxSnoozeCount(int count) {
|
||||
default_max_snooze_count_ = std::max(0, std::min(10, count));
|
||||
}
|
||||
|
||||
// 静态工具方法
|
||||
std::string AlarmManager::FormatTime(int hour, int minute) {
|
||||
std::ostringstream oss;
|
||||
oss << std::setfill('0') << std::setw(2) << hour
|
||||
<< ":" << std::setfill('0') << std::setw(2) << minute;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string AlarmManager::FormatAlarmTime(const AlarmItem& alarm) {
|
||||
std::ostringstream oss;
|
||||
oss << FormatTime(alarm.hour, alarm.minute);
|
||||
|
||||
switch (alarm.repeat_mode) {
|
||||
case kAlarmOnce:
|
||||
oss << " (一次)";
|
||||
break;
|
||||
case kAlarmDaily:
|
||||
oss << " (每日)";
|
||||
break;
|
||||
case kAlarmWeekdays:
|
||||
oss << " (工作日)";
|
||||
break;
|
||||
case kAlarmWeekends:
|
||||
oss << " (周末)";
|
||||
break;
|
||||
case kAlarmCustom:
|
||||
oss << " (自定义)";
|
||||
break;
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool AlarmManager::IsWeekdayActive(const AlarmItem& alarm, int weekday) {
|
||||
if (alarm.repeat_mode == kAlarmOnce) {
|
||||
return true; // 一次性闹钟在任何一天都可以触发
|
||||
}
|
||||
|
||||
return (alarm.weekdays_mask & (1 << weekday)) != 0;
|
||||
}
|
||||
|
||||
// 私有方法实现
|
||||
void AlarmManager::LoadAlarmsFromStorage() {
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
alarms_.clear();
|
||||
|
||||
// 读取闹钟数量
|
||||
int alarm_count = settings_->GetInt("count", 0);
|
||||
ESP_LOGI(TAG, "Loading %d alarms from storage", alarm_count);
|
||||
|
||||
for (int i = 0; i < alarm_count; i++) {
|
||||
std::string alarm_key = "alarm_" + std::to_string(i);
|
||||
std::string alarm_json = settings_->GetString(alarm_key);
|
||||
|
||||
if (alarm_json.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cJSON* json = cJSON_Parse(alarm_json.c_str());
|
||||
if (!json) {
|
||||
ESP_LOGW(TAG, "Failed to parse alarm JSON: %s", alarm_key.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto alarm = std::make_unique<AlarmItem>();
|
||||
|
||||
// 解析JSON数据
|
||||
if (auto id = cJSON_GetObjectItem(json, "id")) alarm->id = id->valueint;
|
||||
if (auto hour = cJSON_GetObjectItem(json, "hour")) alarm->hour = hour->valueint;
|
||||
if (auto minute = cJSON_GetObjectItem(json, "minute")) alarm->minute = minute->valueint;
|
||||
if (auto repeat = cJSON_GetObjectItem(json, "repeat")) alarm->repeat_mode = (AlarmRepeatMode)repeat->valueint;
|
||||
if (auto mask = cJSON_GetObjectItem(json, "weekdays")) alarm->weekdays_mask = mask->valueint;
|
||||
if (auto status = cJSON_GetObjectItem(json, "status")) alarm->status = (AlarmStatus)status->valueint;
|
||||
if (auto label = cJSON_GetObjectItem(json, "label")) alarm->label = label->valuestring;
|
||||
if (auto music = cJSON_GetObjectItem(json, "music")) alarm->music_name = music->valuestring;
|
||||
if (auto snooze_min = cJSON_GetObjectItem(json, "snooze_minutes")) alarm->snooze_minutes = snooze_min->valueint;
|
||||
if (auto max_snooze = cJSON_GetObjectItem(json, "max_snooze")) alarm->max_snooze_count = max_snooze->valueint;
|
||||
|
||||
// 重置运行时状态
|
||||
alarm->snooze_count = 0;
|
||||
alarm->last_triggered_time = 0;
|
||||
alarm->next_snooze_time = 0;
|
||||
if (alarm->status == kAlarmTriggered || alarm->status == kAlarmSnoozed) {
|
||||
alarm->status = kAlarmEnabled;
|
||||
}
|
||||
|
||||
alarms_.push_back(std::move(alarm));
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Loaded %d alarms successfully", alarms_.size());
|
||||
}
|
||||
|
||||
void AlarmManager::SaveAlarmsToStorage() {
|
||||
if (!settings_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(alarms_mutex_);
|
||||
|
||||
// 保存闹钟数量
|
||||
settings_->SetInt("count", alarms_.size());
|
||||
|
||||
// 保存每个闹钟
|
||||
for (size_t i = 0; i < alarms_.size(); i++) {
|
||||
std::string alarm_key = "alarm_" + std::to_string(i);
|
||||
SaveAlarmToStorage(*alarms_[i]);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Saved %d alarms to storage", alarms_.size());
|
||||
}
|
||||
|
||||
void AlarmManager::SaveAlarmToStorage(const AlarmItem& alarm) {
|
||||
if (!settings_) {
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON* json = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddNumberToObject(json, "id", alarm.id);
|
||||
cJSON_AddNumberToObject(json, "hour", alarm.hour);
|
||||
cJSON_AddNumberToObject(json, "minute", alarm.minute);
|
||||
cJSON_AddNumberToObject(json, "repeat", alarm.repeat_mode);
|
||||
cJSON_AddNumberToObject(json, "weekdays", alarm.weekdays_mask);
|
||||
cJSON_AddNumberToObject(json, "status", alarm.status);
|
||||
cJSON_AddStringToObject(json, "label", alarm.label.c_str());
|
||||
cJSON_AddStringToObject(json, "music", alarm.music_name.c_str());
|
||||
cJSON_AddNumberToObject(json, "snooze_minutes", alarm.snooze_minutes);
|
||||
cJSON_AddNumberToObject(json, "max_snooze", alarm.max_snooze_count);
|
||||
|
||||
char* json_string = cJSON_PrintUnformatted(json);
|
||||
|
||||
// 找到这个闹钟在数组中的位置
|
||||
auto it = std::find_if(alarms_.begin(), alarms_.end(),
|
||||
[&alarm](const auto& a) { return a->id == alarm.id; });
|
||||
|
||||
if (it != alarms_.end()) {
|
||||
size_t index = std::distance(alarms_.begin(), it);
|
||||
std::string alarm_key = "alarm_" + std::to_string(index);
|
||||
settings_->SetString(alarm_key, json_string);
|
||||
}
|
||||
|
||||
cJSON_free(json_string);
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
|
||||
void AlarmManager::RemoveAlarmFromStorage(int alarm_id) {
|
||||
// 重新保存所有闹钟(简单实现)
|
||||
SaveAlarmsToStorage();
|
||||
}
|
||||
|
||||
int AlarmManager::GetNextAlarmId() {
|
||||
return next_alarm_id_++;
|
||||
}
|
||||
|
||||
bool AlarmManager::ShouldTriggerToday(const AlarmItem& alarm) const {
|
||||
if (alarm.repeat_mode == kAlarmOnce) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int weekday = GetCurrentWeekday();
|
||||
return IsWeekdayActive(alarm, weekday);
|
||||
}
|
||||
|
||||
int64_t AlarmManager::GetCurrentTimeInMinutes() const {
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm* timeinfo = localtime(&now);
|
||||
return timeinfo->tm_hour * 60 + timeinfo->tm_min;
|
||||
}
|
||||
|
||||
int AlarmManager::GetCurrentWeekday() const {
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm* timeinfo = localtime(&now);
|
||||
return timeinfo->tm_wday; // 0=周日, 1=周一, ..., 6=周六
|
||||
}
|
||||
140
main/alarm_manager.h
Normal file
140
main/alarm_manager.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#ifndef ALARM_MANAGER_H
|
||||
#define ALARM_MANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <esp_timer.h>
|
||||
#include "settings.h"
|
||||
|
||||
// 闹钟重复模式
|
||||
enum AlarmRepeatMode {
|
||||
kAlarmOnce = 0, // 一次性闹钟
|
||||
kAlarmDaily = 1, // 每日重复
|
||||
kAlarmWeekdays = 2, // 工作日(周一到周五)
|
||||
kAlarmWeekends = 3, // 周末(周六周日)
|
||||
kAlarmCustom = 4 // 自定义星期(使用weekdays_mask)
|
||||
};
|
||||
|
||||
// 闹钟状态
|
||||
enum AlarmStatus {
|
||||
kAlarmEnabled = 0, // 启用
|
||||
kAlarmDisabled = 1, // 禁用
|
||||
kAlarmTriggered = 2, // 已触发(等待贪睡或关闭)
|
||||
kAlarmSnoozed = 3 // 贪睡中
|
||||
};
|
||||
|
||||
// 闹钟项结构
|
||||
struct AlarmItem {
|
||||
int id; // 闹钟ID
|
||||
int hour; // 小时 (0-23)
|
||||
int minute; // 分钟 (0-59)
|
||||
AlarmRepeatMode repeat_mode; // 重复模式
|
||||
uint8_t weekdays_mask; // 星期掩码 (bit0=周日, bit1=周一, ..., bit6=周六)
|
||||
AlarmStatus status; // 闹钟状态
|
||||
std::string label; // 闹钟标签/备注
|
||||
std::string music_name; // 指定的音乐名称(空则使用默认铃声)
|
||||
int snooze_count; // 当前贪睡次数
|
||||
int max_snooze_count; // 最大贪睡次数 (默认3次)
|
||||
int snooze_minutes; // 贪睡间隔(分钟,默认5分钟)
|
||||
int64_t last_triggered_time; // 上次触发时间戳(避免重复触发)
|
||||
int64_t next_snooze_time; // 下次贪睡时间戳
|
||||
|
||||
AlarmItem() : id(0), hour(0), minute(0), repeat_mode(kAlarmOnce),
|
||||
weekdays_mask(0), status(kAlarmEnabled), label(""),
|
||||
music_name(""), snooze_count(0), max_snooze_count(3),
|
||||
snooze_minutes(5), last_triggered_time(0), next_snooze_time(0) {}
|
||||
};
|
||||
|
||||
// 闹钟触发回调类型
|
||||
using AlarmTriggeredCallback = std::function<void(const AlarmItem& alarm)>;
|
||||
using AlarmSnoozeCallback = std::function<void(const AlarmItem& alarm)>;
|
||||
using AlarmStopCallback = std::function<void(const AlarmItem& alarm)>;
|
||||
|
||||
class AlarmManager {
|
||||
public:
|
||||
static AlarmManager& GetInstance() {
|
||||
static AlarmManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
AlarmManager(const AlarmManager&) = delete;
|
||||
AlarmManager& operator=(const AlarmManager&) = delete;
|
||||
|
||||
// 初始化和清理
|
||||
void Initialize();
|
||||
void Cleanup();
|
||||
|
||||
// 闹钟管理
|
||||
int AddAlarm(int hour, int minute, AlarmRepeatMode repeat_mode = kAlarmOnce,
|
||||
const std::string& label = "", const std::string& music_name = "");
|
||||
bool RemoveAlarm(int alarm_id);
|
||||
bool EnableAlarm(int alarm_id, bool enabled = true);
|
||||
bool ModifyAlarm(int alarm_id, int hour, int minute, AlarmRepeatMode repeat_mode = kAlarmOnce,
|
||||
const std::string& label = "", const std::string& music_name = "");
|
||||
|
||||
// 查询功能
|
||||
std::vector<AlarmItem> GetAllAlarms() const;
|
||||
AlarmItem* GetAlarm(int alarm_id);
|
||||
std::vector<AlarmItem> GetActiveAlarms() const;
|
||||
std::string GetNextAlarmInfo() const; // 获取下一个闹钟的信息字符串
|
||||
|
||||
// 贪睡和停止
|
||||
bool SnoozeAlarm(int alarm_id);
|
||||
bool StopAlarm(int alarm_id);
|
||||
void StopAllActiveAlarms();
|
||||
|
||||
// 时间检查 (由Application的CLOCK_TICK调用)
|
||||
void CheckAlarms();
|
||||
|
||||
// 回调设置
|
||||
void SetAlarmTriggeredCallback(AlarmTriggeredCallback callback);
|
||||
void SetAlarmSnoozeCallback(AlarmSnoozeCallback callback);
|
||||
void SetAlarmStopCallback(AlarmStopCallback callback);
|
||||
|
||||
// 配置设置
|
||||
void SetDefaultSnoozeMinutes(int minutes);
|
||||
void SetDefaultMaxSnoozeCount(int count);
|
||||
|
||||
// 时间工具
|
||||
static std::string FormatTime(int hour, int minute);
|
||||
static std::string FormatAlarmTime(const AlarmItem& alarm);
|
||||
static bool IsWeekdayActive(const AlarmItem& alarm, int weekday); // 0=周日, 1=周一, ..., 6=周六
|
||||
|
||||
private:
|
||||
AlarmManager();
|
||||
~AlarmManager();
|
||||
|
||||
// 内部方法
|
||||
void LoadAlarmsFromStorage();
|
||||
void SaveAlarmsToStorage();
|
||||
void SaveAlarmToStorage(const AlarmItem& alarm);
|
||||
void RemoveAlarmFromStorage(int alarm_id);
|
||||
|
||||
int GetNextAlarmId();
|
||||
bool ShouldTriggerToday(const AlarmItem& alarm) const;
|
||||
int64_t GetCurrentTimeInMinutes() const; // 获取当前时间的分钟数(从午夜开始)
|
||||
int GetCurrentWeekday() const; // 获取当前星期几
|
||||
|
||||
// 成员变量
|
||||
std::vector<std::unique_ptr<AlarmItem>> alarms_;
|
||||
std::unique_ptr<Settings> settings_;
|
||||
bool initialized_;
|
||||
int next_alarm_id_;
|
||||
|
||||
// 配置
|
||||
int default_snooze_minutes_;
|
||||
int default_max_snooze_count_;
|
||||
|
||||
// 回调函数
|
||||
AlarmTriggeredCallback on_alarm_triggered_;
|
||||
AlarmSnoozeCallback on_alarm_snoozed_;
|
||||
AlarmStopCallback on_alarm_stopped_;
|
||||
|
||||
// 互斥锁保护
|
||||
mutable std::mutex alarms_mutex_;
|
||||
};
|
||||
|
||||
#endif // ALARM_MANAGER_H
|
||||
2204
main/application.cc
2204
main/application.cc
File diff suppressed because it is too large
Load Diff
@@ -1,111 +1,129 @@
|
||||
#ifndef _APPLICATION_H_
|
||||
#define _APPLICATION_H_
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
#include "protocol.h"
|
||||
#include "ota.h"
|
||||
#include "audio_service.h"
|
||||
#include "device_state_event.h"
|
||||
|
||||
|
||||
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
||||
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
||||
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
||||
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
||||
#define MAIN_EVENT_ERROR (1 << 4)
|
||||
#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5)
|
||||
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
||||
|
||||
|
||||
enum AecMode {
|
||||
kAecOff,
|
||||
kAecOnDeviceSide,
|
||||
kAecOnServerSide,
|
||||
};
|
||||
|
||||
class Application {
|
||||
public:
|
||||
static Application& GetInstance() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
void Start();
|
||||
void MainEventLoop();
|
||||
DeviceState GetDeviceState() const { return device_state_; }
|
||||
bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); }
|
||||
void Schedule(std::function<void()> callback);
|
||||
void SetDeviceState(DeviceState state);
|
||||
void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");
|
||||
void DismissAlert();
|
||||
void AbortSpeaking(AbortReason reason);
|
||||
void ToggleChatState();
|
||||
void StartListening();
|
||||
void StopListening();
|
||||
void Reboot();
|
||||
void WakeWordInvoke(const std::string& wake_word);
|
||||
bool UpgradeFirmware(Ota& ota, const std::string& url = "");
|
||||
bool CanEnterSleepMode();
|
||||
void SendMcpMessage(const std::string& payload);
|
||||
void SetAecMode(AecMode mode);
|
||||
AecMode GetAecMode() const { return aec_mode_; }
|
||||
void PlaySound(const std::string_view& sound);
|
||||
AudioService& GetAudioService() { return audio_service_; }
|
||||
void AddAudioData(AudioStreamPacket&& packet);
|
||||
|
||||
private:
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
std::mutex mutex_;
|
||||
std::deque<std::function<void()>> main_tasks_;
|
||||
std::unique_ptr<Protocol> protocol_;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
esp_timer_handle_t clock_timer_handle_ = nullptr;
|
||||
volatile DeviceState device_state_ = kDeviceStateUnknown;
|
||||
ListeningMode listening_mode_ = kListeningModeAutoStop;
|
||||
AecMode aec_mode_ = kAecOff;
|
||||
std::string last_error_message_;
|
||||
AudioService audio_service_;
|
||||
|
||||
bool has_server_time_ = false;
|
||||
bool aborted_ = false;
|
||||
int clock_ticks_ = 0;
|
||||
TaskHandle_t check_new_version_task_handle_ = nullptr;
|
||||
TaskHandle_t main_event_loop_task_handle_ = nullptr;
|
||||
|
||||
void OnWakeWordDetected();
|
||||
void CheckNewVersion(Ota& ota);
|
||||
void CheckAssetsVersion();
|
||||
void ShowActivationCode(const std::string& code, const std::string& message);
|
||||
void SetListeningMode(ListeningMode mode);
|
||||
};
|
||||
|
||||
|
||||
class TaskPriorityReset {
|
||||
public:
|
||||
TaskPriorityReset(BaseType_t priority) {
|
||||
original_priority_ = uxTaskPriorityGet(NULL);
|
||||
vTaskPrioritySet(NULL, priority);
|
||||
}
|
||||
~TaskPriorityReset() {
|
||||
vTaskPrioritySet(NULL, original_priority_);
|
||||
}
|
||||
|
||||
private:
|
||||
BaseType_t original_priority_;
|
||||
};
|
||||
|
||||
#endif // _APPLICATION_H_
|
||||
#ifndef _APPLICATION_H_
|
||||
#define _APPLICATION_H_
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
#include "protocol.h"
|
||||
#include "ota.h"
|
||||
#include "audio_service.h"
|
||||
#include "device_state_event.h"
|
||||
#include "alarm_manager.h"
|
||||
|
||||
|
||||
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
||||
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
||||
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
||||
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
||||
#define MAIN_EVENT_ERROR (1 << 4)
|
||||
#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5)
|
||||
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
||||
|
||||
|
||||
enum AecMode {
|
||||
kAecOff,
|
||||
kAecOnDeviceSide,
|
||||
kAecOnServerSide,
|
||||
};
|
||||
|
||||
class Application {
|
||||
public:
|
||||
static Application& GetInstance() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
void Start();
|
||||
void MainEventLoop();
|
||||
DeviceState GetDeviceState() const { return device_state_; }
|
||||
bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); }
|
||||
void Schedule(std::function<void()> callback);
|
||||
void SetDeviceState(DeviceState state);
|
||||
void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");
|
||||
void DismissAlert();
|
||||
void AbortSpeaking(AbortReason reason);
|
||||
void ToggleChatState();
|
||||
void StartListening();
|
||||
void StopListening();
|
||||
void Reboot();
|
||||
void WakeWordInvoke(const std::string& wake_word);
|
||||
bool UpgradeFirmware(Ota& ota, const std::string& url = "");
|
||||
bool CanEnterSleepMode();
|
||||
void SendMcpMessage(const std::string& payload);
|
||||
void SetAecMode(AecMode mode);
|
||||
AecMode GetAecMode() const { return aec_mode_; }
|
||||
void PlaySound(const std::string_view& sound);
|
||||
// 新增:接收外部音频数据(如音乐播放)
|
||||
void AddAudioData(AudioStreamPacket&& packet);
|
||||
AudioService& GetAudioService() { return audio_service_; }
|
||||
|
||||
// 闹钟功能
|
||||
AlarmManager& GetAlarmManager() { return AlarmManager::GetInstance(); }
|
||||
std::vector<std::string> GetDefaultAlarmMusicList() const;
|
||||
|
||||
private:
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
std::mutex mutex_;
|
||||
std::deque<std::function<void()>> main_tasks_;
|
||||
std::unique_ptr<Protocol> protocol_;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
esp_timer_handle_t clock_timer_handle_ = nullptr;
|
||||
volatile DeviceState device_state_ = kDeviceStateUnknown;
|
||||
ListeningMode listening_mode_ = kListeningModeAutoStop;
|
||||
AecMode aec_mode_ = kAecOff;
|
||||
std::string last_error_message_;
|
||||
AudioService audio_service_;
|
||||
|
||||
bool has_server_time_ = false;
|
||||
bool aborted_ = false;
|
||||
int clock_ticks_ = 0;
|
||||
TaskHandle_t check_new_version_task_handle_ = nullptr;
|
||||
TaskHandle_t main_event_loop_task_handle_ = nullptr;
|
||||
|
||||
void OnWakeWordDetected();
|
||||
void CheckNewVersion(Ota& ota);
|
||||
void CheckAssetsVersion();
|
||||
void ShowActivationCode(const std::string& code, const std::string& message);
|
||||
void SetListeningMode(ListeningMode mode);
|
||||
|
||||
// 闹钟相关私有方法
|
||||
void OnAlarmTriggered(const AlarmItem& alarm);
|
||||
void OnAlarmSnoozed(const AlarmItem& alarm);
|
||||
void OnAlarmStopped(const AlarmItem& alarm);
|
||||
|
||||
// 音乐进度跟踪相关
|
||||
void UpdateMusicProgress();
|
||||
std::string current_music_name_;
|
||||
int64_t music_start_time_ms_ = 0; // 音乐开始播放的时间戳
|
||||
int music_duration_seconds_ = 180; // 默认歌曲长度(3分钟)
|
||||
bool is_music_playing_ = false;
|
||||
};
|
||||
|
||||
|
||||
class TaskPriorityReset {
|
||||
public:
|
||||
TaskPriorityReset(BaseType_t priority) {
|
||||
original_priority_ = uxTaskPriorityGet(NULL);
|
||||
vTaskPrioritySet(NULL, priority);
|
||||
}
|
||||
~TaskPriorityReset() {
|
||||
vTaskPrioritySet(NULL, original_priority_);
|
||||
}
|
||||
|
||||
private:
|
||||
BaseType_t original_priority_;
|
||||
};
|
||||
|
||||
#endif // _APPLICATION_H_
|
||||
|
||||
923
main/assets.cc
923
main/assets.cc
@@ -1,406 +1,517 @@
|
||||
#include "assets.h"
|
||||
#include "board.h"
|
||||
#include "display.h"
|
||||
#include "application.h"
|
||||
#include "lvgl_theme.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <spi_flash_mmap.h>
|
||||
#include <esp_timer.h>
|
||||
#include <cbin_font.h>
|
||||
|
||||
|
||||
#define TAG "Assets"
|
||||
|
||||
struct mmap_assets_table {
|
||||
char asset_name[32]; /*!< Name of the asset */
|
||||
uint32_t asset_size; /*!< Size of the asset */
|
||||
uint32_t asset_offset; /*!< Offset of the asset */
|
||||
uint16_t asset_width; /*!< Width of the asset */
|
||||
uint16_t asset_height; /*!< Height of the asset */
|
||||
};
|
||||
|
||||
|
||||
Assets::Assets(std::string default_assets_url) {
|
||||
if (default_assets_url.find("http") == 0) {
|
||||
default_assets_url_ = default_assets_url;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The default assets url is not a http url: %s", default_assets_url.c_str());
|
||||
}
|
||||
|
||||
// Initialize the partition
|
||||
InitializePartition();
|
||||
}
|
||||
|
||||
Assets::~Assets() {
|
||||
if (mmap_handle_ != 0) {
|
||||
esp_partition_munmap(mmap_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Assets::CalculateChecksum(const char* data, uint32_t length) {
|
||||
uint32_t checksum = 0;
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
return checksum & 0xFFFF;
|
||||
}
|
||||
|
||||
bool Assets::InitializePartition() {
|
||||
partition_valid_ = false;
|
||||
checksum_valid_ = false;
|
||||
assets_.clear();
|
||||
|
||||
partition_ = esp_partition_find_first(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, "assets");
|
||||
if (partition_ == nullptr) {
|
||||
ESP_LOGI(TAG, "No assets partition found");
|
||||
return false;
|
||||
}
|
||||
|
||||
int free_pages = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
|
||||
uint32_t storage_size = free_pages * 64 * 1024;
|
||||
ESP_LOGI(TAG, "The storage free size is %ld KB", storage_size / 1024);
|
||||
ESP_LOGI(TAG, "The partition size is %ld KB", partition_->size / 1024);
|
||||
if (storage_size < partition_->size) {
|
||||
ESP_LOGE(TAG, "The free size %ld KB is less than assets partition required %ld KB", storage_size / 1024, partition_->size / 1024);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_partition_mmap(partition_, 0, partition_->size, ESP_PARTITION_MMAP_DATA, (const void**)&mmap_root_, &mmap_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to mmap assets partition: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
partition_valid_ = true;
|
||||
|
||||
uint32_t stored_files = *(uint32_t*)(mmap_root_ + 0);
|
||||
uint32_t stored_chksum = *(uint32_t*)(mmap_root_ + 4);
|
||||
uint32_t stored_len = *(uint32_t*)(mmap_root_ + 8);
|
||||
|
||||
if (stored_len > partition_->size - 12) {
|
||||
ESP_LOGD(TAG, "The stored_len (0x%lx) is greater than the partition size (0x%lx) - 12", stored_len, partition_->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto start_time = esp_timer_get_time();
|
||||
uint32_t calculated_checksum = CalculateChecksum(mmap_root_ + 12, stored_len);
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "The checksum calculation time is %d ms", int((end_time - start_time) / 1000));
|
||||
|
||||
if (calculated_checksum != stored_chksum) {
|
||||
ESP_LOGE(TAG, "The calculated checksum (0x%lx) does not match the stored checksum (0x%lx)", calculated_checksum, stored_chksum);
|
||||
return false;
|
||||
}
|
||||
|
||||
checksum_valid_ = true;
|
||||
|
||||
for (uint32_t i = 0; i < stored_files; i++) {
|
||||
auto item = (const mmap_assets_table*)(mmap_root_ + 12 + i * sizeof(mmap_assets_table));
|
||||
auto asset = Asset{
|
||||
.size = static_cast<size_t>(item->asset_size),
|
||||
.offset = static_cast<size_t>(12 + sizeof(mmap_assets_table) * stored_files + item->asset_offset)
|
||||
};
|
||||
assets_[item->asset_name] = asset;
|
||||
}
|
||||
return checksum_valid_;
|
||||
}
|
||||
|
||||
bool Assets::Apply() {
|
||||
void* ptr = nullptr;
|
||||
size_t size = 0;
|
||||
if (!GetAssetData("index.json", ptr, size)) {
|
||||
ESP_LOGE(TAG, "The index.json file is not found");
|
||||
return false;
|
||||
}
|
||||
cJSON* root = cJSON_ParseWithLength(static_cast<char*>(ptr), size);
|
||||
if (root == nullptr) {
|
||||
ESP_LOGE(TAG, "The index.json file is not valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON* version = cJSON_GetObjectItem(root, "version");
|
||||
if (cJSON_IsNumber(version)) {
|
||||
if (version->valuedouble > 1) {
|
||||
ESP_LOGE(TAG, "The assets version %d is not supported, please upgrade the firmware", version->valueint);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* srmodels = cJSON_GetObjectItem(root, "srmodels");
|
||||
if (cJSON_IsString(srmodels)) {
|
||||
std::string srmodels_file = srmodels->valuestring;
|
||||
if (GetAssetData(srmodels_file, ptr, size)) {
|
||||
if (models_list_ != nullptr) {
|
||||
esp_srmodel_deinit(models_list_);
|
||||
models_list_ = nullptr;
|
||||
}
|
||||
models_list_ = srmodel_load(static_cast<uint8_t*>(ptr));
|
||||
if (models_list_ != nullptr) {
|
||||
auto& app = Application::GetInstance();
|
||||
app.GetAudioService().SetModelsList(models_list_);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to load srmodels.bin");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The srmodels file %s is not found", srmodels_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_LVGL
|
||||
auto& theme_manager = LvglThemeManager::GetInstance();
|
||||
auto light_theme = theme_manager.GetTheme("light");
|
||||
auto dark_theme = theme_manager.GetTheme("dark");
|
||||
|
||||
cJSON* font = cJSON_GetObjectItem(root, "text_font");
|
||||
if (cJSON_IsString(font)) {
|
||||
std::string fonts_text_file = font->valuestring;
|
||||
if (GetAssetData(fonts_text_file, ptr, size)) {
|
||||
auto text_font = std::make_shared<LvglCBinFont>(ptr);
|
||||
if (text_font->font() == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to load fonts.bin");
|
||||
return false;
|
||||
}
|
||||
if (light_theme != nullptr) {
|
||||
light_theme->set_text_font(text_font);
|
||||
}
|
||||
if (dark_theme != nullptr) {
|
||||
dark_theme->set_text_font(text_font);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection");
|
||||
if (cJSON_IsArray(emoji_collection)) {
|
||||
auto custom_emoji_collection = std::make_shared<EmojiCollection>();
|
||||
int emoji_count = cJSON_GetArraySize(emoji_collection);
|
||||
for (int i = 0; i < emoji_count; i++) {
|
||||
cJSON* emoji = cJSON_GetArrayItem(emoji_collection, i);
|
||||
if (cJSON_IsObject(emoji)) {
|
||||
cJSON* name = cJSON_GetObjectItem(emoji, "name");
|
||||
cJSON* file = cJSON_GetObjectItem(emoji, "file");
|
||||
if (cJSON_IsString(name) && cJSON_IsString(file)) {
|
||||
if (!GetAssetData(file->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "Emoji %s image file %s is not found", name->valuestring, file->valuestring);
|
||||
continue;
|
||||
}
|
||||
custom_emoji_collection->AddEmoji(name->valuestring, new LvglRawImage(ptr, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (light_theme != nullptr) {
|
||||
light_theme->set_emoji_collection(custom_emoji_collection);
|
||||
}
|
||||
if (dark_theme != nullptr) {
|
||||
dark_theme->set_emoji_collection(custom_emoji_collection);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* skin = cJSON_GetObjectItem(root, "skin");
|
||||
if (cJSON_IsObject(skin)) {
|
||||
cJSON* light_skin = cJSON_GetObjectItem(skin, "light");
|
||||
if (cJSON_IsObject(light_skin) && light_theme != nullptr) {
|
||||
cJSON* text_color = cJSON_GetObjectItem(light_skin, "text_color");
|
||||
cJSON* background_color = cJSON_GetObjectItem(light_skin, "background_color");
|
||||
cJSON* background_image = cJSON_GetObjectItem(light_skin, "background_image");
|
||||
if (cJSON_IsString(text_color)) {
|
||||
light_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_color)) {
|
||||
light_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
light_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_image)) {
|
||||
if (!GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
|
||||
return false;
|
||||
}
|
||||
auto background_image = std::make_shared<LvglCBinImage>(ptr);
|
||||
light_theme->set_background_image(background_image);
|
||||
}
|
||||
}
|
||||
cJSON* dark_skin = cJSON_GetObjectItem(skin, "dark");
|
||||
if (cJSON_IsObject(dark_skin) && dark_theme != nullptr) {
|
||||
cJSON* text_color = cJSON_GetObjectItem(dark_skin, "text_color");
|
||||
cJSON* background_color = cJSON_GetObjectItem(dark_skin, "background_color");
|
||||
cJSON* background_image = cJSON_GetObjectItem(dark_skin, "background_image");
|
||||
if (cJSON_IsString(text_color)) {
|
||||
dark_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_color)) {
|
||||
dark_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
dark_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_image)) {
|
||||
if (!GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
|
||||
return false;
|
||||
}
|
||||
auto background_image = std::make_shared<LvglCBinImage>(ptr);
|
||||
dark_theme->set_background_image(background_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
ESP_LOGI(TAG, "Refreshing display theme...");
|
||||
|
||||
auto current_theme = display->GetTheme();
|
||||
if (current_theme != nullptr) {
|
||||
display->SetTheme(current_theme);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::Download(std::string url, std::function<void(int progress, size_t speed)> progress_callback) {
|
||||
ESP_LOGI(TAG, "Downloading new version of assets from %s", url.c_str());
|
||||
|
||||
// 取消当前资源分区的内存映射
|
||||
if (mmap_handle_ != 0) {
|
||||
esp_partition_munmap(mmap_handle_);
|
||||
mmap_handle_ = 0;
|
||||
mmap_root_ = nullptr;
|
||||
}
|
||||
checksum_valid_ = false;
|
||||
assets_.clear();
|
||||
|
||||
// 下载新的资源文件
|
||||
auto network = Board::GetInstance().GetNetwork();
|
||||
auto http = network->CreateHttp(0);
|
||||
|
||||
if (!http->Open("GET", url)) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (http->GetStatusCode() != 200) {
|
||||
ESP_LOGE(TAG, "Failed to get assets, status code: %d", http->GetStatusCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t content_length = http->GetBodyLength();
|
||||
if (content_length == 0) {
|
||||
ESP_LOGE(TAG, "Failed to get content length");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (content_length > partition_->size) {
|
||||
ESP_LOGE(TAG, "Assets file size (%u) is larger than partition size (%lu)", content_length, partition_->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 定义扇区大小为4KB(ESP32的标准扇区大小)
|
||||
const size_t SECTOR_SIZE = esp_partition_get_main_flash_sector_size();
|
||||
|
||||
// 计算需要擦除的扇区数量
|
||||
size_t sectors_to_erase = (content_length + SECTOR_SIZE - 1) / SECTOR_SIZE; // 向上取整
|
||||
size_t total_erase_size = sectors_to_erase * SECTOR_SIZE;
|
||||
|
||||
ESP_LOGI(TAG, "Sector size: %u, content length: %u, sectors to erase: %u, total erase size: %u",
|
||||
SECTOR_SIZE, content_length, sectors_to_erase, total_erase_size);
|
||||
|
||||
// 写入新的资源文件到分区,一边erase一边写入
|
||||
char buffer[512];
|
||||
size_t total_written = 0;
|
||||
size_t recent_written = 0;
|
||||
size_t current_sector = 0;
|
||||
auto last_calc_time = esp_timer_get_time();
|
||||
|
||||
while (true) {
|
||||
int ret = http->Read(buffer, sizeof(buffer));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查是否需要擦除新的扇区
|
||||
size_t write_end_offset = total_written + ret;
|
||||
size_t needed_sectors = (write_end_offset + SECTOR_SIZE - 1) / SECTOR_SIZE;
|
||||
|
||||
// 擦除需要的新扇区
|
||||
while (current_sector < needed_sectors) {
|
||||
size_t sector_start = current_sector * SECTOR_SIZE;
|
||||
size_t sector_end = (current_sector + 1) * SECTOR_SIZE;
|
||||
|
||||
// 确保擦除范围不超过分区大小
|
||||
if (sector_end > partition_->size) {
|
||||
ESP_LOGE(TAG, "Sector end (%u) exceeds partition size (%lu)", sector_end, partition_->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Erasing sector %u (offset: %u, size: %u)", current_sector, sector_start, SECTOR_SIZE);
|
||||
esp_err_t err = esp_partition_erase_range(partition_, sector_start, SECTOR_SIZE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to erase sector %u at offset %u: %s", current_sector, sector_start, esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
current_sector++;
|
||||
}
|
||||
|
||||
// 写入数据到分区
|
||||
esp_err_t err = esp_partition_write(partition_, total_written, buffer, ret);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write to assets partition at offset %u: %s", total_written, esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
total_written += ret;
|
||||
recent_written += ret;
|
||||
|
||||
// 计算进度和速度
|
||||
if (esp_timer_get_time() - last_calc_time >= 1000000 || total_written == content_length || ret == 0) {
|
||||
size_t progress = total_written * 100 / content_length;
|
||||
size_t speed = recent_written; // 每秒的字节数
|
||||
ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %u B/s, Sectors erased: %u",
|
||||
progress, total_written, content_length, speed, current_sector);
|
||||
if (progress_callback) {
|
||||
progress_callback(progress, speed);
|
||||
}
|
||||
last_calc_time = esp_timer_get_time();
|
||||
recent_written = 0; // 重置最近写入的字节数
|
||||
}
|
||||
}
|
||||
|
||||
http->Close();
|
||||
|
||||
if (total_written != content_length) {
|
||||
ESP_LOGE(TAG, "Downloaded size (%u) does not match expected size (%u)", total_written, content_length);
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Assets download completed, total written: %u bytes, total sectors erased: %u",
|
||||
total_written, current_sector);
|
||||
|
||||
// 重新初始化资源分区
|
||||
if (!InitializePartition()) {
|
||||
ESP_LOGE(TAG, "Failed to re-initialize assets partition");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::GetAssetData(const std::string& name, void*& ptr, size_t& size) {
|
||||
auto asset = assets_.find(name);
|
||||
if (asset == assets_.end()) {
|
||||
return false;
|
||||
}
|
||||
auto data = (const char*)(mmap_root_ + asset->second.offset);
|
||||
if (data[0] != 'Z' || data[1] != 'Z') {
|
||||
ESP_LOGE(TAG, "The asset %s is not valid with magic %02x%02x", name.c_str(), data[0], data[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = static_cast<void*>(const_cast<char*>(data + 2));
|
||||
size = asset->second.size;
|
||||
return true;
|
||||
}
|
||||
#include "assets.h"
|
||||
#include "board.h"
|
||||
#include "display.h"
|
||||
#include "application.h"
|
||||
#include "lvgl_theme.h"
|
||||
#include "emote_display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <spi_flash_mmap.h>
|
||||
#include <esp_timer.h>
|
||||
#include <cbin_font.h>
|
||||
|
||||
|
||||
#define TAG "Assets"
|
||||
|
||||
struct mmap_assets_table {
|
||||
char asset_name[32]; /*!< Name of the asset */
|
||||
uint32_t asset_size; /*!< Size of the asset */
|
||||
uint32_t asset_offset; /*!< Offset of the asset */
|
||||
uint16_t asset_width; /*!< Width of the asset */
|
||||
uint16_t asset_height; /*!< Height of the asset */
|
||||
};
|
||||
|
||||
|
||||
Assets::Assets() {
|
||||
// Initialize the partition
|
||||
InitializePartition();
|
||||
}
|
||||
|
||||
Assets::~Assets() {
|
||||
if (mmap_handle_ != 0) {
|
||||
esp_partition_munmap(mmap_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Assets::CalculateChecksum(const char* data, uint32_t length) {
|
||||
uint32_t checksum = 0;
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
return checksum & 0xFFFF;
|
||||
}
|
||||
|
||||
bool Assets::InitializePartition() {
|
||||
partition_valid_ = false;
|
||||
checksum_valid_ = false;
|
||||
assets_.clear();
|
||||
|
||||
partition_ = esp_partition_find_first(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, "assets");
|
||||
if (partition_ == nullptr) {
|
||||
ESP_LOGI(TAG, "No assets partition found");
|
||||
return false;
|
||||
}
|
||||
|
||||
int free_pages = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
|
||||
uint32_t storage_size = free_pages * 64 * 1024;
|
||||
ESP_LOGI(TAG, "The storage free size is %ld KB", storage_size / 1024);
|
||||
ESP_LOGI(TAG, "The partition size is %ld KB", partition_->size / 1024);
|
||||
if (storage_size < partition_->size) {
|
||||
ESP_LOGE(TAG, "The free size %ld KB is less than assets partition required %ld KB", storage_size / 1024, partition_->size / 1024);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_partition_mmap(partition_, 0, partition_->size, ESP_PARTITION_MMAP_DATA, (const void**)&mmap_root_, &mmap_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to mmap assets partition: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
partition_valid_ = true;
|
||||
|
||||
uint32_t stored_files = *(uint32_t*)(mmap_root_ + 0);
|
||||
uint32_t stored_chksum = *(uint32_t*)(mmap_root_ + 4);
|
||||
uint32_t stored_len = *(uint32_t*)(mmap_root_ + 8);
|
||||
|
||||
if (stored_len > partition_->size - 12) {
|
||||
ESP_LOGD(TAG, "The stored_len (0x%lx) is greater than the partition size (0x%lx) - 12", stored_len, partition_->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto start_time = esp_timer_get_time();
|
||||
uint32_t calculated_checksum = CalculateChecksum(mmap_root_ + 12, stored_len);
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "The checksum calculation time is %d ms", int((end_time - start_time) / 1000));
|
||||
|
||||
if (calculated_checksum != stored_chksum) {
|
||||
ESP_LOGE(TAG, "The calculated checksum (0x%lx) does not match the stored checksum (0x%lx)", calculated_checksum, stored_chksum);
|
||||
return false;
|
||||
}
|
||||
|
||||
checksum_valid_ = true;
|
||||
|
||||
for (uint32_t i = 0; i < stored_files; i++) {
|
||||
auto item = (const mmap_assets_table*)(mmap_root_ + 12 + i * sizeof(mmap_assets_table));
|
||||
auto asset = Asset{
|
||||
.size = static_cast<size_t>(item->asset_size),
|
||||
.offset = static_cast<size_t>(12 + sizeof(mmap_assets_table) * stored_files + item->asset_offset)
|
||||
};
|
||||
assets_[item->asset_name] = asset;
|
||||
}
|
||||
return checksum_valid_;
|
||||
}
|
||||
|
||||
bool Assets::Apply() {
|
||||
void* ptr = nullptr;
|
||||
size_t size = 0;
|
||||
if (!GetAssetData("index.json", ptr, size)) {
|
||||
ESP_LOGE(TAG, "The index.json file is not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_ParseWithLength(static_cast<char*>(ptr), size);
|
||||
if (root == nullptr) {
|
||||
ESP_LOGE(TAG, "The index.json file is not valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON* version = cJSON_GetObjectItem(root, "version");
|
||||
if (cJSON_IsNumber(version)) {
|
||||
if (version->valuedouble > 1) {
|
||||
ESP_LOGE(TAG, "The assets version %d is not supported, please upgrade the firmware", version->valueint);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* srmodels = cJSON_GetObjectItem(root, "srmodels");
|
||||
if (cJSON_IsString(srmodels)) {
|
||||
std::string srmodels_file = srmodels->valuestring;
|
||||
if (GetAssetData(srmodels_file, ptr, size)) {
|
||||
if (models_list_ != nullptr) {
|
||||
esp_srmodel_deinit(models_list_);
|
||||
models_list_ = nullptr;
|
||||
}
|
||||
models_list_ = srmodel_load(static_cast<uint8_t*>(ptr));
|
||||
if (models_list_ != nullptr) {
|
||||
auto& app = Application::GetInstance();
|
||||
app.GetAudioService().SetModelsList(models_list_);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to load srmodels.bin");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The srmodels file %s is not found", srmodels_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_LVGL
|
||||
auto& theme_manager = LvglThemeManager::GetInstance();
|
||||
auto light_theme = theme_manager.GetTheme("light");
|
||||
auto dark_theme = theme_manager.GetTheme("dark");
|
||||
|
||||
cJSON* font = cJSON_GetObjectItem(root, "text_font");
|
||||
if (cJSON_IsString(font)) {
|
||||
std::string fonts_text_file = font->valuestring;
|
||||
if (GetAssetData(fonts_text_file, ptr, size)) {
|
||||
auto text_font = std::make_shared<LvglCBinFont>(ptr);
|
||||
if (text_font->font() == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to load fonts.bin");
|
||||
return false;
|
||||
}
|
||||
if (light_theme != nullptr) {
|
||||
light_theme->set_text_font(text_font);
|
||||
}
|
||||
if (dark_theme != nullptr) {
|
||||
dark_theme->set_text_font(text_font);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection");
|
||||
if (cJSON_IsArray(emoji_collection)) {
|
||||
auto custom_emoji_collection = std::make_shared<EmojiCollection>();
|
||||
int emoji_count = cJSON_GetArraySize(emoji_collection);
|
||||
for (int i = 0; i < emoji_count; i++) {
|
||||
cJSON* emoji = cJSON_GetArrayItem(emoji_collection, i);
|
||||
if (cJSON_IsObject(emoji)) {
|
||||
cJSON* name = cJSON_GetObjectItem(emoji, "name");
|
||||
cJSON* file = cJSON_GetObjectItem(emoji, "file");
|
||||
cJSON* eaf = cJSON_GetObjectItem(emoji, "eaf");
|
||||
if (cJSON_IsString(name) && cJSON_IsString(file) && (NULL== eaf)) {
|
||||
if (!GetAssetData(file->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "Emoji %s image file %s is not found", name->valuestring, file->valuestring);
|
||||
continue;
|
||||
}
|
||||
custom_emoji_collection->AddEmoji(name->valuestring, new LvglRawImage(ptr, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (light_theme != nullptr) {
|
||||
light_theme->set_emoji_collection(custom_emoji_collection);
|
||||
}
|
||||
if (dark_theme != nullptr) {
|
||||
dark_theme->set_emoji_collection(custom_emoji_collection);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* skin = cJSON_GetObjectItem(root, "skin");
|
||||
if (cJSON_IsObject(skin)) {
|
||||
cJSON* light_skin = cJSON_GetObjectItem(skin, "light");
|
||||
if (cJSON_IsObject(light_skin) && light_theme != nullptr) {
|
||||
cJSON* text_color = cJSON_GetObjectItem(light_skin, "text_color");
|
||||
cJSON* background_color = cJSON_GetObjectItem(light_skin, "background_color");
|
||||
cJSON* background_image = cJSON_GetObjectItem(light_skin, "background_image");
|
||||
if (cJSON_IsString(text_color)) {
|
||||
light_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_color)) {
|
||||
light_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
light_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_image)) {
|
||||
if (!GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
|
||||
return false;
|
||||
}
|
||||
auto background_image = std::make_shared<LvglCBinImage>(ptr);
|
||||
light_theme->set_background_image(background_image);
|
||||
}
|
||||
}
|
||||
cJSON* dark_skin = cJSON_GetObjectItem(skin, "dark");
|
||||
if (cJSON_IsObject(dark_skin) && dark_theme != nullptr) {
|
||||
cJSON* text_color = cJSON_GetObjectItem(dark_skin, "text_color");
|
||||
cJSON* background_color = cJSON_GetObjectItem(dark_skin, "background_color");
|
||||
cJSON* background_image = cJSON_GetObjectItem(dark_skin, "background_image");
|
||||
if (cJSON_IsString(text_color)) {
|
||||
dark_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_color)) {
|
||||
dark_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
dark_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_image)) {
|
||||
if (!GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
|
||||
return false;
|
||||
}
|
||||
auto background_image = std::make_shared<LvglCBinImage>(ptr);
|
||||
dark_theme->set_background_image(background_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
ESP_LOGI(TAG, "Refreshing display theme...");
|
||||
|
||||
auto current_theme = display->GetTheme();
|
||||
if (current_theme != nullptr) {
|
||||
display->SetTheme(current_theme);
|
||||
}
|
||||
#elif defined(CONFIG_USE_EMOTE_MESSAGE_STYLE)
|
||||
auto &board = Board::GetInstance();
|
||||
auto display = board.GetDisplay();
|
||||
auto emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
|
||||
|
||||
cJSON* font = cJSON_GetObjectItem(root, "text_font");
|
||||
if (cJSON_IsString(font)) {
|
||||
std::string fonts_text_file = font->valuestring;
|
||||
if (GetAssetData(fonts_text_file, ptr, size)) {
|
||||
auto text_font = std::make_shared<LvglCBinFont>(ptr);
|
||||
if (text_font->font() == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to load fonts.bin");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (emote_display) {
|
||||
emote_display->AddTextFont(text_font);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection");
|
||||
if (cJSON_IsArray(emoji_collection)) {
|
||||
int emoji_count = cJSON_GetArraySize(emoji_collection);
|
||||
if (emote_display) {
|
||||
for (int i = 0; i < emoji_count; i++) {
|
||||
cJSON* icon = cJSON_GetArrayItem(emoji_collection, i);
|
||||
if (cJSON_IsObject(icon)) {
|
||||
cJSON* name = cJSON_GetObjectItem(icon, "name");
|
||||
cJSON* file = cJSON_GetObjectItem(icon, "file");
|
||||
|
||||
if (cJSON_IsString(name) && cJSON_IsString(file)) {
|
||||
if (GetAssetData(file->valuestring, ptr, size)) {
|
||||
cJSON* eaf = cJSON_GetObjectItem(icon, "eaf");
|
||||
bool lack_value = false;
|
||||
bool loop_value = false;
|
||||
int fps_value = 0;
|
||||
|
||||
if (cJSON_IsObject(eaf)) {
|
||||
cJSON* lack = cJSON_GetObjectItem(eaf, "lack");
|
||||
cJSON* loop = cJSON_GetObjectItem(eaf, "loop");
|
||||
cJSON* fps = cJSON_GetObjectItem(eaf, "fps");
|
||||
|
||||
lack_value = lack ? cJSON_IsTrue(lack) : false;
|
||||
loop_value = loop ? cJSON_IsTrue(loop) : false;
|
||||
fps_value = fps ? fps->valueint : 0;
|
||||
|
||||
emote_display->AddEmojiData(name->valuestring, ptr, size,
|
||||
static_cast<uint8_t>(fps_value),
|
||||
loop_value, lack_value);
|
||||
}
|
||||
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Emoji \"%10s\" image file %s is not found", name->valuestring, file->valuestring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* icon_collection = cJSON_GetObjectItem(root, "icon_collection");
|
||||
if (cJSON_IsArray(icon_collection)) {
|
||||
if (emote_display) {
|
||||
int icon_count = cJSON_GetArraySize(icon_collection);
|
||||
for (int i = 0; i < icon_count; i++) {
|
||||
cJSON* icon = cJSON_GetArrayItem(icon_collection, i);
|
||||
if (cJSON_IsObject(icon)) {
|
||||
cJSON* name = cJSON_GetObjectItem(icon, "name");
|
||||
cJSON* file = cJSON_GetObjectItem(icon, "file");
|
||||
|
||||
if (cJSON_IsString(name) && cJSON_IsString(file)) {
|
||||
if (GetAssetData(file->valuestring, ptr, size)) {
|
||||
emote_display->AddIconData(name->valuestring, ptr, size);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Icon \"%10s\" image file %s is not found", name->valuestring, file->valuestring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* layout_json = cJSON_GetObjectItem(root, "layout");
|
||||
if (cJSON_IsArray(layout_json)) {
|
||||
int layout_count = cJSON_GetArraySize(layout_json);
|
||||
|
||||
for (int i = 0; i < layout_count; i++) {
|
||||
cJSON* layout_item = cJSON_GetArrayItem(layout_json, i);
|
||||
if (cJSON_IsObject(layout_item)) {
|
||||
cJSON* name = cJSON_GetObjectItem(layout_item, "name");
|
||||
cJSON* align = cJSON_GetObjectItem(layout_item, "align");
|
||||
cJSON* x = cJSON_GetObjectItem(layout_item, "x");
|
||||
cJSON* y = cJSON_GetObjectItem(layout_item, "y");
|
||||
cJSON* width = cJSON_GetObjectItem(layout_item, "width");
|
||||
cJSON* height = cJSON_GetObjectItem(layout_item, "height");
|
||||
|
||||
if (cJSON_IsString(name) && cJSON_IsString(align) && cJSON_IsNumber(x) && cJSON_IsNumber(y)) {
|
||||
int width_val = cJSON_IsNumber(width) ? width->valueint : 0;
|
||||
int height_val = cJSON_IsNumber(height) ? height->valueint : 0;
|
||||
|
||||
if (emote_display) {
|
||||
emote_display->AddLayoutData(name->valuestring, align->valuestring,
|
||||
x->valueint, y->valueint, width_val, height_val);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid layout item %d: missing required fields", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
cJSON_Delete(root);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::Download(std::string url, std::function<void(int progress, size_t speed)> progress_callback) {
|
||||
ESP_LOGI(TAG, "Downloading new version of assets from %s", url.c_str());
|
||||
|
||||
// 取消当前资源分区的内存映射
|
||||
if (mmap_handle_ != 0) {
|
||||
esp_partition_munmap(mmap_handle_);
|
||||
mmap_handle_ = 0;
|
||||
mmap_root_ = nullptr;
|
||||
}
|
||||
checksum_valid_ = false;
|
||||
assets_.clear();
|
||||
|
||||
// 下载新的资源文件
|
||||
auto network = Board::GetInstance().GetNetwork();
|
||||
auto http = network->CreateHttp(0);
|
||||
|
||||
if (!http->Open("GET", url)) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (http->GetStatusCode() != 200) {
|
||||
ESP_LOGE(TAG, "Failed to get assets, status code: %d", http->GetStatusCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t content_length = http->GetBodyLength();
|
||||
if (content_length == 0) {
|
||||
ESP_LOGE(TAG, "Failed to get content length");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (content_length > partition_->size) {
|
||||
ESP_LOGE(TAG, "Assets file size (%u) is larger than partition size (%lu)", content_length, partition_->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 定义扇区大小为4KB(ESP32的标准扇区大小)
|
||||
const size_t SECTOR_SIZE = esp_partition_get_main_flash_sector_size();
|
||||
|
||||
// 计算需要擦除的扇区数量
|
||||
size_t sectors_to_erase = (content_length + SECTOR_SIZE - 1) / SECTOR_SIZE; // 向上取整
|
||||
size_t total_erase_size = sectors_to_erase * SECTOR_SIZE;
|
||||
|
||||
ESP_LOGI(TAG, "Sector size: %u, content length: %u, sectors to erase: %u, total erase size: %u",
|
||||
SECTOR_SIZE, content_length, sectors_to_erase, total_erase_size);
|
||||
|
||||
// 写入新的资源文件到分区,一边erase一边写入
|
||||
char buffer[512];
|
||||
size_t total_written = 0;
|
||||
size_t recent_written = 0;
|
||||
size_t current_sector = 0;
|
||||
auto last_calc_time = esp_timer_get_time();
|
||||
|
||||
while (true) {
|
||||
int ret = http->Read(buffer, sizeof(buffer));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查是否需要擦除新的扇区
|
||||
size_t write_end_offset = total_written + ret;
|
||||
size_t needed_sectors = (write_end_offset + SECTOR_SIZE - 1) / SECTOR_SIZE;
|
||||
|
||||
// 擦除需要的新扇区
|
||||
while (current_sector < needed_sectors) {
|
||||
size_t sector_start = current_sector * SECTOR_SIZE;
|
||||
size_t sector_end = (current_sector + 1) * SECTOR_SIZE;
|
||||
|
||||
// 确保擦除范围不超过分区大小
|
||||
if (sector_end > partition_->size) {
|
||||
ESP_LOGE(TAG, "Sector end (%u) exceeds partition size (%lu)", sector_end, partition_->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Erasing sector %u (offset: %u, size: %u)", current_sector, sector_start, SECTOR_SIZE);
|
||||
esp_err_t err = esp_partition_erase_range(partition_, sector_start, SECTOR_SIZE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to erase sector %u at offset %u: %s", current_sector, sector_start, esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
current_sector++;
|
||||
}
|
||||
|
||||
// 写入数据到分区
|
||||
esp_err_t err = esp_partition_write(partition_, total_written, buffer, ret);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write to assets partition at offset %u: %s", total_written, esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
total_written += ret;
|
||||
recent_written += ret;
|
||||
|
||||
// 计算进度和速度
|
||||
if (esp_timer_get_time() - last_calc_time >= 1000000 || total_written == content_length || ret == 0) {
|
||||
size_t progress = total_written * 100 / content_length;
|
||||
size_t speed = recent_written; // 每秒的字节数
|
||||
ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %u B/s, Sectors erased: %u",
|
||||
progress, total_written, content_length, speed, current_sector);
|
||||
if (progress_callback) {
|
||||
progress_callback(progress, speed);
|
||||
}
|
||||
last_calc_time = esp_timer_get_time();
|
||||
recent_written = 0; // 重置最近写入的字节数
|
||||
}
|
||||
}
|
||||
|
||||
http->Close();
|
||||
|
||||
if (total_written != content_length) {
|
||||
ESP_LOGE(TAG, "Downloaded size (%u) does not match expected size (%u)", total_written, content_length);
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Assets download completed, total written: %u bytes, total sectors erased: %u",
|
||||
total_written, current_sector);
|
||||
|
||||
// 重新初始化资源分区
|
||||
if (!InitializePartition()) {
|
||||
ESP_LOGE(TAG, "Failed to re-initialize assets partition");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::GetAssetData(const std::string& name, void*& ptr, size_t& size) {
|
||||
auto asset = assets_.find(name);
|
||||
if (asset == assets_.end()) {
|
||||
return false;
|
||||
}
|
||||
auto data = (const char*)(mmap_root_ + asset->second.offset);
|
||||
if (data[0] != 'Z' || data[1] != 'Z') {
|
||||
ESP_LOGE(TAG, "The asset %s is not valid with magic %02x%02x", name.c_str(), data[0], data[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = static_cast<void*>(const_cast<char*>(data + 2));
|
||||
size = asset->second.size;
|
||||
return true;
|
||||
}
|
||||
|
||||
117
main/assets.h
117
main/assets.h
@@ -1,65 +1,52 @@
|
||||
#ifndef ASSETS_H
|
||||
#define ASSETS_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <esp_partition.h>
|
||||
#include <model_path.h>
|
||||
|
||||
|
||||
// All combinations of wakenet_model, text_font, emoji_collection can be found from the following url:
|
||||
// https://github.com/78/xiaozhi-fonts/releases/tag/assets
|
||||
|
||||
#define ASSETS_PUHUI_COMMON_14_1 "none-font_puhui_common_14_1-none.bin"
|
||||
#define ASSETS_XIAOZHI_WAKENET "wn9_nihaoxiaozhi_tts-none-none.bin"
|
||||
#define ASSETS_XIAOZHI_WAKENET_SMALL "wn9s_nihaoxiaozhi-none-none.bin"
|
||||
#define ASSETS_XIAOZHI_PUHUI_COMMON_14_1 "wn9_nihaoxiaozhi_tts-font_puhui_common_14_1-none.bin"
|
||||
#define ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_32 "wn9_nihaoxiaozhi_tts-font_puhui_common_16_4-emojis_32.bin"
|
||||
#define ASSETS_XIAOZHI_PUHUI_COMMON_16_4_EMOJI_64 "wn9_nihaoxiaozhi_tts-font_puhui_common_16_4-emojis_64.bin"
|
||||
#define ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64 "wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-emojis_64.bin"
|
||||
#define ASSETS_XIAOZHI_PUHUI_COMMON_30_4_EMOJI_64 "wn9_nihaoxiaozhi_tts-font_puhui_common_30_4-emojis_64.bin"
|
||||
#define ASSETS_XIAOZHI_S_PUHUI_COMMON_14_1 "wn9s_nihaoxiaozhi-font_puhui_common_14_1-none.bin"
|
||||
#define ASSETS_XIAOZHI_S_PUHUI_COMMON_16_4_EMOJI_32 "wn9s_nihaoxiaozhi-font_puhui_common_16_4-emojis_32.bin"
|
||||
#define ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_32 "wn9s_nihaoxiaozhi-font_puhui_common_20_4-emojis_32.bin"
|
||||
#define ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_64 "wn9s_nihaoxiaozhi-font_puhui_common_20_4-emojis_64.bin"
|
||||
#define ASSETS_XIAOZHI_S_PUHUI_COMMON_30_4_EMOJI_64 "wn9s_nihaoxiaozhi-font_puhui_common_30_4-emojis_64.bin"
|
||||
|
||||
struct Asset {
|
||||
size_t size;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
class Assets {
|
||||
public:
|
||||
Assets(std::string default_assets_url);
|
||||
~Assets();
|
||||
|
||||
bool Download(std::string url, std::function<void(int progress, size_t speed)> progress_callback);
|
||||
bool Apply();
|
||||
|
||||
inline bool partition_valid() const { return partition_valid_; }
|
||||
inline bool checksum_valid() const { return checksum_valid_; }
|
||||
inline std::string default_assets_url() const { return default_assets_url_; }
|
||||
|
||||
private:
|
||||
Assets(const Assets&) = delete;
|
||||
Assets& operator=(const Assets&) = delete;
|
||||
|
||||
bool InitializePartition();
|
||||
uint32_t CalculateChecksum(const char* data, uint32_t length);
|
||||
bool GetAssetData(const std::string& name, void*& ptr, size_t& size);
|
||||
|
||||
const esp_partition_t* partition_ = nullptr;
|
||||
esp_partition_mmap_handle_t mmap_handle_ = 0;
|
||||
const char* mmap_root_ = nullptr;
|
||||
bool partition_valid_ = false;
|
||||
bool checksum_valid_ = false;
|
||||
std::string default_assets_url_;
|
||||
srmodel_list_t* models_list_ = nullptr;
|
||||
std::map<std::string, Asset> assets_;
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifndef ASSETS_H
|
||||
#define ASSETS_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <esp_partition.h>
|
||||
#include <model_path.h>
|
||||
|
||||
|
||||
struct Asset {
|
||||
size_t size;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
class Assets {
|
||||
public:
|
||||
static Assets& GetInstance() {
|
||||
static Assets instance;
|
||||
return instance;
|
||||
}
|
||||
~Assets();
|
||||
|
||||
bool Download(std::string url, std::function<void(int progress, size_t speed)> progress_callback);
|
||||
bool Apply();
|
||||
bool GetAssetData(const std::string& name, void*& ptr, size_t& size);
|
||||
|
||||
inline bool partition_valid() const { return partition_valid_; }
|
||||
inline bool checksum_valid() const { return checksum_valid_; }
|
||||
inline std::string default_assets_url() const { return default_assets_url_; }
|
||||
|
||||
private:
|
||||
Assets();
|
||||
Assets(const Assets&) = delete;
|
||||
Assets& operator=(const Assets&) = delete;
|
||||
|
||||
bool InitializePartition();
|
||||
uint32_t CalculateChecksum(const char* data, uint32_t length);
|
||||
|
||||
const esp_partition_t* partition_ = nullptr;
|
||||
esp_partition_mmap_handle_t mmap_handle_ = 0;
|
||||
const char* mmap_root_ = nullptr;
|
||||
bool partition_valid_ = false;
|
||||
bool checksum_valid_ = false;
|
||||
std::string default_assets_url_;
|
||||
srmodel_list_t* models_list_ = nullptr;
|
||||
std::map<std::string, Asset> assets_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,218 +1,218 @@
|
||||
// Auto-generated language config
|
||||
// Language: zh-CN with en-US fallback
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#ifndef zh_cn
|
||||
#define zh_cn // 預設語言
|
||||
#endif
|
||||
|
||||
namespace Lang {
|
||||
// 语言元数据
|
||||
constexpr const char* CODE = "zh-CN";
|
||||
|
||||
// 字符串资源 (en-US as fallback for missing keys)
|
||||
namespace Strings {
|
||||
constexpr const char* ACCESS_VIA_BROWSER = ",浏览器访问 ";
|
||||
constexpr const char* ACTIVATION = "激活设备";
|
||||
constexpr const char* BATTERY_CHARGING = "正在充电";
|
||||
constexpr const char* BATTERY_FULL = "电量已满";
|
||||
constexpr const char* BATTERY_LOW = "电量不足";
|
||||
constexpr const char* BATTERY_NEED_CHARGE = "电量低,请充电";
|
||||
constexpr const char* CHECKING_NEW_VERSION = "检查新版本...";
|
||||
constexpr const char* CHECK_NEW_VERSION_FAILED = "检查新版本失败,将在 %d 秒后重试:%s";
|
||||
constexpr const char* CONNECTED_TO = "已连接 ";
|
||||
constexpr const char* CONNECTING = "连接中...";
|
||||
constexpr const char* CONNECTION_SUCCESSFUL = "Connection Successful";
|
||||
constexpr const char* CONNECT_TO = "连接 ";
|
||||
constexpr const char* CONNECT_TO_HOTSPOT = "手机连接热点 ";
|
||||
constexpr const char* DETECTING_MODULE = "检测模组...";
|
||||
constexpr const char* DOWNLOAD_ASSETS_FAILED = "下载资源失败";
|
||||
constexpr const char* ENTERING_WIFI_CONFIG_MODE = "进入配网模式...";
|
||||
constexpr const char* ERROR = "错误";
|
||||
constexpr const char* FOUND_NEW_ASSETS = "发现新资源: %s";
|
||||
constexpr const char* HELLO_MY_FRIEND = "你好,我的朋友!";
|
||||
constexpr const char* INFO = "信息";
|
||||
constexpr const char* INITIALIZING = "正在初始化...";
|
||||
constexpr const char* LISTENING = "聆听中...";
|
||||
constexpr const char* LOADING_ASSETS = "加载资源...";
|
||||
constexpr const char* LOADING_PROTOCOL = "登录服务器...";
|
||||
constexpr const char* MAX_VOLUME = "最大音量";
|
||||
constexpr const char* MUTED = "已静音";
|
||||
constexpr const char* NEW_VERSION = "新版本 ";
|
||||
constexpr const char* OTA_UPGRADE = "OTA 升级";
|
||||
constexpr const char* PIN_ERROR = "请插入 SIM 卡";
|
||||
constexpr const char* PLEASE_WAIT = "请稍候...";
|
||||
constexpr const char* REGISTERING_NETWORK = "等待网络...";
|
||||
constexpr const char* REG_ERROR = "无法接入网络,请检查流量卡状态";
|
||||
constexpr const char* RTC_MODE_OFF = "AEC 关闭";
|
||||
constexpr const char* RTC_MODE_ON = "AEC 开启";
|
||||
constexpr const char* SCANNING_WIFI = "扫描 Wi-Fi...";
|
||||
constexpr const char* SERVER_ERROR = "发送失败,请检查网络";
|
||||
constexpr const char* SERVER_NOT_CONNECTED = "无法连接服务,请稍后再试";
|
||||
constexpr const char* SERVER_NOT_FOUND = "正在寻找可用服务";
|
||||
constexpr const char* SERVER_TIMEOUT = "等待响应超时";
|
||||
constexpr const char* SPEAKING = "说话中...";
|
||||
constexpr const char* STANDBY = "待命";
|
||||
constexpr const char* SWITCH_TO_4G_NETWORK = "切换到 4G...";
|
||||
constexpr const char* SWITCH_TO_WIFI_NETWORK = "切换到 Wi-Fi...";
|
||||
constexpr const char* UPGRADE_FAILED = "升级失败";
|
||||
constexpr const char* UPGRADING = "正在升级系统...";
|
||||
constexpr const char* VERSION = "版本 ";
|
||||
constexpr const char* VOLUME = "音量 ";
|
||||
constexpr const char* WARNING = "警告";
|
||||
constexpr const char* WIFI_CONFIG_MODE = "配网模式";
|
||||
}
|
||||
|
||||
// 音效资源 (en-US as fallback for missing audio files)
|
||||
namespace Sounds {
|
||||
|
||||
extern const char ogg_0_start[] asm("_binary_0_ogg_start");
|
||||
extern const char ogg_0_end[] asm("_binary_0_ogg_end");
|
||||
static const std::string_view OGG_0 {
|
||||
static_cast<const char*>(ogg_0_start),
|
||||
static_cast<size_t>(ogg_0_end - ogg_0_start)
|
||||
};
|
||||
|
||||
extern const char ogg_1_start[] asm("_binary_1_ogg_start");
|
||||
extern const char ogg_1_end[] asm("_binary_1_ogg_end");
|
||||
static const std::string_view OGG_1 {
|
||||
static_cast<const char*>(ogg_1_start),
|
||||
static_cast<size_t>(ogg_1_end - ogg_1_start)
|
||||
};
|
||||
|
||||
extern const char ogg_2_start[] asm("_binary_2_ogg_start");
|
||||
extern const char ogg_2_end[] asm("_binary_2_ogg_end");
|
||||
static const std::string_view OGG_2 {
|
||||
static_cast<const char*>(ogg_2_start),
|
||||
static_cast<size_t>(ogg_2_end - ogg_2_start)
|
||||
};
|
||||
|
||||
extern const char ogg_3_start[] asm("_binary_3_ogg_start");
|
||||
extern const char ogg_3_end[] asm("_binary_3_ogg_end");
|
||||
static const std::string_view OGG_3 {
|
||||
static_cast<const char*>(ogg_3_start),
|
||||
static_cast<size_t>(ogg_3_end - ogg_3_start)
|
||||
};
|
||||
|
||||
extern const char ogg_4_start[] asm("_binary_4_ogg_start");
|
||||
extern const char ogg_4_end[] asm("_binary_4_ogg_end");
|
||||
static const std::string_view OGG_4 {
|
||||
static_cast<const char*>(ogg_4_start),
|
||||
static_cast<size_t>(ogg_4_end - ogg_4_start)
|
||||
};
|
||||
|
||||
extern const char ogg_5_start[] asm("_binary_5_ogg_start");
|
||||
extern const char ogg_5_end[] asm("_binary_5_ogg_end");
|
||||
static const std::string_view OGG_5 {
|
||||
static_cast<const char*>(ogg_5_start),
|
||||
static_cast<size_t>(ogg_5_end - ogg_5_start)
|
||||
};
|
||||
|
||||
extern const char ogg_6_start[] asm("_binary_6_ogg_start");
|
||||
extern const char ogg_6_end[] asm("_binary_6_ogg_end");
|
||||
static const std::string_view OGG_6 {
|
||||
static_cast<const char*>(ogg_6_start),
|
||||
static_cast<size_t>(ogg_6_end - ogg_6_start)
|
||||
};
|
||||
|
||||
extern const char ogg_7_start[] asm("_binary_7_ogg_start");
|
||||
extern const char ogg_7_end[] asm("_binary_7_ogg_end");
|
||||
static const std::string_view OGG_7 {
|
||||
static_cast<const char*>(ogg_7_start),
|
||||
static_cast<size_t>(ogg_7_end - ogg_7_start)
|
||||
};
|
||||
|
||||
extern const char ogg_8_start[] asm("_binary_8_ogg_start");
|
||||
extern const char ogg_8_end[] asm("_binary_8_ogg_end");
|
||||
static const std::string_view OGG_8 {
|
||||
static_cast<const char*>(ogg_8_start),
|
||||
static_cast<size_t>(ogg_8_end - ogg_8_start)
|
||||
};
|
||||
|
||||
extern const char ogg_9_start[] asm("_binary_9_ogg_start");
|
||||
extern const char ogg_9_end[] asm("_binary_9_ogg_end");
|
||||
static const std::string_view OGG_9 {
|
||||
static_cast<const char*>(ogg_9_start),
|
||||
static_cast<size_t>(ogg_9_end - ogg_9_start)
|
||||
};
|
||||
|
||||
extern const char ogg_activation_start[] asm("_binary_activation_ogg_start");
|
||||
extern const char ogg_activation_end[] asm("_binary_activation_ogg_end");
|
||||
static const std::string_view OGG_ACTIVATION {
|
||||
static_cast<const char*>(ogg_activation_start),
|
||||
static_cast<size_t>(ogg_activation_end - ogg_activation_start)
|
||||
};
|
||||
|
||||
extern const char ogg_err_pin_start[] asm("_binary_err_pin_ogg_start");
|
||||
extern const char ogg_err_pin_end[] asm("_binary_err_pin_ogg_end");
|
||||
static const std::string_view OGG_ERR_PIN {
|
||||
static_cast<const char*>(ogg_err_pin_start),
|
||||
static_cast<size_t>(ogg_err_pin_end - ogg_err_pin_start)
|
||||
};
|
||||
|
||||
extern const char ogg_err_reg_start[] asm("_binary_err_reg_ogg_start");
|
||||
extern const char ogg_err_reg_end[] asm("_binary_err_reg_ogg_end");
|
||||
static const std::string_view OGG_ERR_REG {
|
||||
static_cast<const char*>(ogg_err_reg_start),
|
||||
static_cast<size_t>(ogg_err_reg_end - ogg_err_reg_start)
|
||||
};
|
||||
|
||||
extern const char ogg_exclamation_start[] asm("_binary_exclamation_ogg_start");
|
||||
extern const char ogg_exclamation_end[] asm("_binary_exclamation_ogg_end");
|
||||
static const std::string_view OGG_EXCLAMATION {
|
||||
static_cast<const char*>(ogg_exclamation_start),
|
||||
static_cast<size_t>(ogg_exclamation_end - ogg_exclamation_start)
|
||||
};
|
||||
|
||||
extern const char ogg_low_battery_start[] asm("_binary_low_battery_ogg_start");
|
||||
extern const char ogg_low_battery_end[] asm("_binary_low_battery_ogg_end");
|
||||
static const std::string_view OGG_LOW_BATTERY {
|
||||
static_cast<const char*>(ogg_low_battery_start),
|
||||
static_cast<size_t>(ogg_low_battery_end - ogg_low_battery_start)
|
||||
};
|
||||
|
||||
extern const char ogg_popup_start[] asm("_binary_popup_ogg_start");
|
||||
extern const char ogg_popup_end[] asm("_binary_popup_ogg_end");
|
||||
static const std::string_view OGG_POPUP {
|
||||
static_cast<const char*>(ogg_popup_start),
|
||||
static_cast<size_t>(ogg_popup_end - ogg_popup_start)
|
||||
};
|
||||
|
||||
extern const char ogg_success_start[] asm("_binary_success_ogg_start");
|
||||
extern const char ogg_success_end[] asm("_binary_success_ogg_end");
|
||||
static const std::string_view OGG_SUCCESS {
|
||||
static_cast<const char*>(ogg_success_start),
|
||||
static_cast<size_t>(ogg_success_end - ogg_success_start)
|
||||
};
|
||||
|
||||
extern const char ogg_upgrade_start[] asm("_binary_upgrade_ogg_start");
|
||||
extern const char ogg_upgrade_end[] asm("_binary_upgrade_ogg_end");
|
||||
static const std::string_view OGG_UPGRADE {
|
||||
static_cast<const char*>(ogg_upgrade_start),
|
||||
static_cast<size_t>(ogg_upgrade_end - ogg_upgrade_start)
|
||||
};
|
||||
|
||||
extern const char ogg_vibration_start[] asm("_binary_vibration_ogg_start");
|
||||
extern const char ogg_vibration_end[] asm("_binary_vibration_ogg_end");
|
||||
static const std::string_view OGG_VIBRATION {
|
||||
static_cast<const char*>(ogg_vibration_start),
|
||||
static_cast<size_t>(ogg_vibration_end - ogg_vibration_start)
|
||||
};
|
||||
|
||||
extern const char ogg_welcome_start[] asm("_binary_welcome_ogg_start");
|
||||
extern const char ogg_welcome_end[] asm("_binary_welcome_ogg_end");
|
||||
static const std::string_view OGG_WELCOME {
|
||||
static_cast<const char*>(ogg_welcome_start),
|
||||
static_cast<size_t>(ogg_welcome_end - ogg_welcome_start)
|
||||
};
|
||||
|
||||
extern const char ogg_wificonfig_start[] asm("_binary_wificonfig_ogg_start");
|
||||
extern const char ogg_wificonfig_end[] asm("_binary_wificonfig_ogg_end");
|
||||
static const std::string_view OGG_WIFICONFIG {
|
||||
static_cast<const char*>(ogg_wificonfig_start),
|
||||
static_cast<size_t>(ogg_wificonfig_end - ogg_wificonfig_start)
|
||||
};
|
||||
}
|
||||
}
|
||||
// Auto-generated language config
|
||||
// Language: en-US with en-US fallback
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#ifndef en_us
|
||||
#define en_us // 預設語言
|
||||
#endif
|
||||
|
||||
namespace Lang {
|
||||
// 语言元数据
|
||||
constexpr const char* CODE = "en-US";
|
||||
|
||||
// 字符串资源 (en-US as fallback for missing keys)
|
||||
namespace Strings {
|
||||
constexpr const char* ACCESS_VIA_BROWSER = " Config URL: ";
|
||||
constexpr const char* ACTIVATION = "Activation";
|
||||
constexpr const char* BATTERY_CHARGING = "Charging";
|
||||
constexpr const char* BATTERY_FULL = "Battery full";
|
||||
constexpr const char* BATTERY_LOW = "Low battery";
|
||||
constexpr const char* BATTERY_NEED_CHARGE = "Low battery, please charge";
|
||||
constexpr const char* CHECKING_NEW_VERSION = "Checking for new version...";
|
||||
constexpr const char* CHECK_NEW_VERSION_FAILED = "Check for new version failed, will retry in %d seconds: %s";
|
||||
constexpr const char* CONNECTED_TO = "Connected to ";
|
||||
constexpr const char* CONNECTING = "Connecting...";
|
||||
constexpr const char* CONNECTION_SUCCESSFUL = "Connection Successful";
|
||||
constexpr const char* CONNECT_TO = "Connect to ";
|
||||
constexpr const char* CONNECT_TO_HOTSPOT = "Hotspot: ";
|
||||
constexpr const char* DETECTING_MODULE = "Detecting module...";
|
||||
constexpr const char* DOWNLOAD_ASSETS_FAILED = "Failed to download assets";
|
||||
constexpr const char* ENTERING_WIFI_CONFIG_MODE = "Entering Wi-Fi configuration mode...";
|
||||
constexpr const char* ERROR = "Error";
|
||||
constexpr const char* FOUND_NEW_ASSETS = "Found new assets: %s";
|
||||
constexpr const char* HELLO_MY_FRIEND = "Hello, my friend!";
|
||||
constexpr const char* INFO = "Information";
|
||||
constexpr const char* INITIALIZING = "Initializing...";
|
||||
constexpr const char* LISTENING = "Listening...";
|
||||
constexpr const char* LOADING_ASSETS = "Loading assets...";
|
||||
constexpr const char* LOADING_PROTOCOL = "Logging in...";
|
||||
constexpr const char* MAX_VOLUME = "Max volume";
|
||||
constexpr const char* MUTED = "Muted";
|
||||
constexpr const char* NEW_VERSION = "New version ";
|
||||
constexpr const char* OTA_UPGRADE = "OTA Upgrade";
|
||||
constexpr const char* PIN_ERROR = "Please insert SIM card";
|
||||
constexpr const char* PLEASE_WAIT = "Please wait...";
|
||||
constexpr const char* REGISTERING_NETWORK = "Waiting for network...";
|
||||
constexpr const char* REG_ERROR = "Unable to access network, please check SIM card status";
|
||||
constexpr const char* RTC_MODE_OFF = "AEC Off";
|
||||
constexpr const char* RTC_MODE_ON = "AEC On";
|
||||
constexpr const char* SCANNING_WIFI = "Scanning Wi-Fi...";
|
||||
constexpr const char* SERVER_ERROR = "Sending failed, please check the network";
|
||||
constexpr const char* SERVER_NOT_CONNECTED = "Unable to connect to service, please try again later";
|
||||
constexpr const char* SERVER_NOT_FOUND = "Looking for available service";
|
||||
constexpr const char* SERVER_TIMEOUT = "Waiting for response timeout";
|
||||
constexpr const char* SPEAKING = "Speaking...";
|
||||
constexpr const char* STANDBY = "Standby";
|
||||
constexpr const char* SWITCH_TO_4G_NETWORK = "Switching to 4G...";
|
||||
constexpr const char* SWITCH_TO_WIFI_NETWORK = "Switching to Wi-Fi...";
|
||||
constexpr const char* UPGRADE_FAILED = "Upgrade failed";
|
||||
constexpr const char* UPGRADING = "System is upgrading...";
|
||||
constexpr const char* VERSION = "Ver ";
|
||||
constexpr const char* VOLUME = "Volume ";
|
||||
constexpr const char* WARNING = "Warning";
|
||||
constexpr const char* WIFI_CONFIG_MODE = "Wi-Fi Configuration Mode";
|
||||
}
|
||||
|
||||
// 音效资源 (en-US as fallback for missing audio files)
|
||||
namespace Sounds {
|
||||
|
||||
extern const char ogg_0_start[] asm("_binary_0_ogg_start");
|
||||
extern const char ogg_0_end[] asm("_binary_0_ogg_end");
|
||||
static const std::string_view OGG_0 {
|
||||
static_cast<const char*>(ogg_0_start),
|
||||
static_cast<size_t>(ogg_0_end - ogg_0_start)
|
||||
};
|
||||
|
||||
extern const char ogg_1_start[] asm("_binary_1_ogg_start");
|
||||
extern const char ogg_1_end[] asm("_binary_1_ogg_end");
|
||||
static const std::string_view OGG_1 {
|
||||
static_cast<const char*>(ogg_1_start),
|
||||
static_cast<size_t>(ogg_1_end - ogg_1_start)
|
||||
};
|
||||
|
||||
extern const char ogg_2_start[] asm("_binary_2_ogg_start");
|
||||
extern const char ogg_2_end[] asm("_binary_2_ogg_end");
|
||||
static const std::string_view OGG_2 {
|
||||
static_cast<const char*>(ogg_2_start),
|
||||
static_cast<size_t>(ogg_2_end - ogg_2_start)
|
||||
};
|
||||
|
||||
extern const char ogg_3_start[] asm("_binary_3_ogg_start");
|
||||
extern const char ogg_3_end[] asm("_binary_3_ogg_end");
|
||||
static const std::string_view OGG_3 {
|
||||
static_cast<const char*>(ogg_3_start),
|
||||
static_cast<size_t>(ogg_3_end - ogg_3_start)
|
||||
};
|
||||
|
||||
extern const char ogg_4_start[] asm("_binary_4_ogg_start");
|
||||
extern const char ogg_4_end[] asm("_binary_4_ogg_end");
|
||||
static const std::string_view OGG_4 {
|
||||
static_cast<const char*>(ogg_4_start),
|
||||
static_cast<size_t>(ogg_4_end - ogg_4_start)
|
||||
};
|
||||
|
||||
extern const char ogg_5_start[] asm("_binary_5_ogg_start");
|
||||
extern const char ogg_5_end[] asm("_binary_5_ogg_end");
|
||||
static const std::string_view OGG_5 {
|
||||
static_cast<const char*>(ogg_5_start),
|
||||
static_cast<size_t>(ogg_5_end - ogg_5_start)
|
||||
};
|
||||
|
||||
extern const char ogg_6_start[] asm("_binary_6_ogg_start");
|
||||
extern const char ogg_6_end[] asm("_binary_6_ogg_end");
|
||||
static const std::string_view OGG_6 {
|
||||
static_cast<const char*>(ogg_6_start),
|
||||
static_cast<size_t>(ogg_6_end - ogg_6_start)
|
||||
};
|
||||
|
||||
extern const char ogg_7_start[] asm("_binary_7_ogg_start");
|
||||
extern const char ogg_7_end[] asm("_binary_7_ogg_end");
|
||||
static const std::string_view OGG_7 {
|
||||
static_cast<const char*>(ogg_7_start),
|
||||
static_cast<size_t>(ogg_7_end - ogg_7_start)
|
||||
};
|
||||
|
||||
extern const char ogg_8_start[] asm("_binary_8_ogg_start");
|
||||
extern const char ogg_8_end[] asm("_binary_8_ogg_end");
|
||||
static const std::string_view OGG_8 {
|
||||
static_cast<const char*>(ogg_8_start),
|
||||
static_cast<size_t>(ogg_8_end - ogg_8_start)
|
||||
};
|
||||
|
||||
extern const char ogg_9_start[] asm("_binary_9_ogg_start");
|
||||
extern const char ogg_9_end[] asm("_binary_9_ogg_end");
|
||||
static const std::string_view OGG_9 {
|
||||
static_cast<const char*>(ogg_9_start),
|
||||
static_cast<size_t>(ogg_9_end - ogg_9_start)
|
||||
};
|
||||
|
||||
extern const char ogg_activation_start[] asm("_binary_activation_ogg_start");
|
||||
extern const char ogg_activation_end[] asm("_binary_activation_ogg_end");
|
||||
static const std::string_view OGG_ACTIVATION {
|
||||
static_cast<const char*>(ogg_activation_start),
|
||||
static_cast<size_t>(ogg_activation_end - ogg_activation_start)
|
||||
};
|
||||
|
||||
extern const char ogg_err_pin_start[] asm("_binary_err_pin_ogg_start");
|
||||
extern const char ogg_err_pin_end[] asm("_binary_err_pin_ogg_end");
|
||||
static const std::string_view OGG_ERR_PIN {
|
||||
static_cast<const char*>(ogg_err_pin_start),
|
||||
static_cast<size_t>(ogg_err_pin_end - ogg_err_pin_start)
|
||||
};
|
||||
|
||||
extern const char ogg_err_reg_start[] asm("_binary_err_reg_ogg_start");
|
||||
extern const char ogg_err_reg_end[] asm("_binary_err_reg_ogg_end");
|
||||
static const std::string_view OGG_ERR_REG {
|
||||
static_cast<const char*>(ogg_err_reg_start),
|
||||
static_cast<size_t>(ogg_err_reg_end - ogg_err_reg_start)
|
||||
};
|
||||
|
||||
extern const char ogg_exclamation_start[] asm("_binary_exclamation_ogg_start");
|
||||
extern const char ogg_exclamation_end[] asm("_binary_exclamation_ogg_end");
|
||||
static const std::string_view OGG_EXCLAMATION {
|
||||
static_cast<const char*>(ogg_exclamation_start),
|
||||
static_cast<size_t>(ogg_exclamation_end - ogg_exclamation_start)
|
||||
};
|
||||
|
||||
extern const char ogg_low_battery_start[] asm("_binary_low_battery_ogg_start");
|
||||
extern const char ogg_low_battery_end[] asm("_binary_low_battery_ogg_end");
|
||||
static const std::string_view OGG_LOW_BATTERY {
|
||||
static_cast<const char*>(ogg_low_battery_start),
|
||||
static_cast<size_t>(ogg_low_battery_end - ogg_low_battery_start)
|
||||
};
|
||||
|
||||
extern const char ogg_popup_start[] asm("_binary_popup_ogg_start");
|
||||
extern const char ogg_popup_end[] asm("_binary_popup_ogg_end");
|
||||
static const std::string_view OGG_POPUP {
|
||||
static_cast<const char*>(ogg_popup_start),
|
||||
static_cast<size_t>(ogg_popup_end - ogg_popup_start)
|
||||
};
|
||||
|
||||
extern const char ogg_success_start[] asm("_binary_success_ogg_start");
|
||||
extern const char ogg_success_end[] asm("_binary_success_ogg_end");
|
||||
static const std::string_view OGG_SUCCESS {
|
||||
static_cast<const char*>(ogg_success_start),
|
||||
static_cast<size_t>(ogg_success_end - ogg_success_start)
|
||||
};
|
||||
|
||||
extern const char ogg_upgrade_start[] asm("_binary_upgrade_ogg_start");
|
||||
extern const char ogg_upgrade_end[] asm("_binary_upgrade_ogg_end");
|
||||
static const std::string_view OGG_UPGRADE {
|
||||
static_cast<const char*>(ogg_upgrade_start),
|
||||
static_cast<size_t>(ogg_upgrade_end - ogg_upgrade_start)
|
||||
};
|
||||
|
||||
extern const char ogg_vibration_start[] asm("_binary_vibration_ogg_start");
|
||||
extern const char ogg_vibration_end[] asm("_binary_vibration_ogg_end");
|
||||
static const std::string_view OGG_VIBRATION {
|
||||
static_cast<const char*>(ogg_vibration_start),
|
||||
static_cast<size_t>(ogg_vibration_end - ogg_vibration_start)
|
||||
};
|
||||
|
||||
extern const char ogg_welcome_start[] asm("_binary_welcome_ogg_start");
|
||||
extern const char ogg_welcome_end[] asm("_binary_welcome_ogg_end");
|
||||
static const std::string_view OGG_WELCOME {
|
||||
static_cast<const char*>(ogg_welcome_start),
|
||||
static_cast<size_t>(ogg_welcome_end - ogg_welcome_start)
|
||||
};
|
||||
|
||||
extern const char ogg_wificonfig_start[] asm("_binary_wificonfig_ogg_start");
|
||||
extern const char ogg_wificonfig_end[] asm("_binary_wificonfig_ogg_end");
|
||||
static const std::string_view OGG_WIFICONFIG {
|
||||
static_cast<const char*>(ogg_wificonfig_start),
|
||||
static_cast<size_t>(ogg_wificonfig_end - ogg_wificonfig_start)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "ar-SA"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "تحذير",
|
||||
"INFO": "معلومات",
|
||||
"ERROR": "خطأ",
|
||||
"VERSION": "الإصدار ",
|
||||
"LOADING_PROTOCOL": "الاتصال بالخادم...",
|
||||
"INITIALIZING": "التهيئة...",
|
||||
"PIN_ERROR": "يرجى إدخال بطاقة SIM",
|
||||
"REG_ERROR": "لا يمكن الوصول إلى الشبكة، يرجى التحقق من حالة بطاقة البيانات",
|
||||
"DETECTING_MODULE": "اكتشاف الوحدة...",
|
||||
"REGISTERING_NETWORK": "انتظار الشبكة...",
|
||||
"CHECKING_NEW_VERSION": "فحص الإصدار الجديد...",
|
||||
"CHECK_NEW_VERSION_FAILED": "فشل فحص الإصدار الجديد، سيتم المحاولة خلال %d ثانية: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "التبديل إلى Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "التبديل إلى 4G...",
|
||||
"STANDBY": "في الانتظار",
|
||||
"CONNECT_TO": "الاتصال بـ ",
|
||||
"CONNECTING": "جاري الاتصال...",
|
||||
"CONNECTED_TO": "متصل بـ ",
|
||||
"LISTENING": "الاستماع...",
|
||||
"SPEAKING": "التحدث...",
|
||||
"SERVER_NOT_FOUND": "البحث عن خدمة متاحة",
|
||||
"SERVER_NOT_CONNECTED": "لا يمكن الاتصال بالخدمة، يرجى المحاولة لاحقاً",
|
||||
"SERVER_TIMEOUT": "انتهت مهلة الاستجابة",
|
||||
"SERVER_ERROR": "فشل الإرسال، يرجى التحقق من الشبكة",
|
||||
"CONNECT_TO_HOTSPOT": "اتصل الهاتف بنقطة الاتصال ",
|
||||
"ACCESS_VIA_BROWSER": "،الوصول عبر المتصفح ",
|
||||
"WIFI_CONFIG_MODE": "وضع تكوين الشبكة",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "الدخول في وضع تكوين الشبكة...",
|
||||
"SCANNING_WIFI": "فحص Wi-Fi...",
|
||||
"NEW_VERSION": "إصدار جديد ",
|
||||
"OTA_UPGRADE": "تحديث OTA",
|
||||
"UPGRADING": "تحديث النظام...",
|
||||
"UPGRADE_FAILED": "فشل التحديث",
|
||||
"ACTIVATION": "تفعيل الجهاز",
|
||||
"BATTERY_LOW": "البطارية منخفضة",
|
||||
"BATTERY_CHARGING": "جاري الشحن",
|
||||
"BATTERY_FULL": "البطارية ممتلئة",
|
||||
"BATTERY_NEED_CHARGE": "البطارية منخفضة، يرجى الشحن",
|
||||
"VOLUME": "الصوت ",
|
||||
"MUTED": "صامت",
|
||||
"MAX_VOLUME": "أقصى صوت",
|
||||
"RTC_MODE_OFF": "AEC مُوقف",
|
||||
"RTC_MODE_ON": "AEC مُشغل",
|
||||
"DOWNLOAD_ASSETS_FAILED": "فشل في تنزيل الموارد",
|
||||
"LOADING_ASSETS": "جاري تحميل الموارد...",
|
||||
"PLEASE_WAIT": "يرجى الانتظار...",
|
||||
"FOUND_NEW_ASSETS": "تم العثور على موارد جديدة: %s",
|
||||
"HELLO_MY_FRIEND": "مرحباً، صديقي!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "ar-SA"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "تحذير",
|
||||
"INFO": "معلومات",
|
||||
"ERROR": "خطأ",
|
||||
"VERSION": "الإصدار ",
|
||||
"LOADING_PROTOCOL": "الاتصال بالخادم...",
|
||||
"INITIALIZING": "التهيئة...",
|
||||
"PIN_ERROR": "يرجى إدخال بطاقة SIM",
|
||||
"REG_ERROR": "لا يمكن الوصول إلى الشبكة، يرجى التحقق من حالة بطاقة البيانات",
|
||||
"DETECTING_MODULE": "اكتشاف الوحدة...",
|
||||
"REGISTERING_NETWORK": "انتظار الشبكة...",
|
||||
"CHECKING_NEW_VERSION": "فحص الإصدار الجديد...",
|
||||
"CHECK_NEW_VERSION_FAILED": "فشل فحص الإصدار الجديد، سيتم المحاولة خلال %d ثانية: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "التبديل إلى Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "التبديل إلى 4G...",
|
||||
"STANDBY": "في الانتظار",
|
||||
"CONNECT_TO": "الاتصال بـ ",
|
||||
"CONNECTING": "جاري الاتصال...",
|
||||
"CONNECTED_TO": "متصل بـ ",
|
||||
"LISTENING": "الاستماع...",
|
||||
"SPEAKING": "التحدث...",
|
||||
"SERVER_NOT_FOUND": "البحث عن خدمة متاحة",
|
||||
"SERVER_NOT_CONNECTED": "لا يمكن الاتصال بالخدمة، يرجى المحاولة لاحقاً",
|
||||
"SERVER_TIMEOUT": "انتهت مهلة الاستجابة",
|
||||
"SERVER_ERROR": "فشل الإرسال، يرجى التحقق من الشبكة",
|
||||
"CONNECT_TO_HOTSPOT": "اتصل الهاتف بنقطة الاتصال ",
|
||||
"ACCESS_VIA_BROWSER": "،الوصول عبر المتصفح ",
|
||||
"WIFI_CONFIG_MODE": "وضع تكوين الشبكة",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "الدخول في وضع تكوين الشبكة...",
|
||||
"SCANNING_WIFI": "فحص Wi-Fi...",
|
||||
"NEW_VERSION": "إصدار جديد ",
|
||||
"OTA_UPGRADE": "تحديث OTA",
|
||||
"UPGRADING": "تحديث النظام...",
|
||||
"UPGRADE_FAILED": "فشل التحديث",
|
||||
"ACTIVATION": "تفعيل الجهاز",
|
||||
"BATTERY_LOW": "البطارية منخفضة",
|
||||
"BATTERY_CHARGING": "جاري الشحن",
|
||||
"BATTERY_FULL": "البطارية ممتلئة",
|
||||
"BATTERY_NEED_CHARGE": "البطارية منخفضة، يرجى الشحن",
|
||||
"VOLUME": "الصوت ",
|
||||
"MUTED": "صامت",
|
||||
"MAX_VOLUME": "أقصى صوت",
|
||||
"RTC_MODE_OFF": "AEC مُوقف",
|
||||
"RTC_MODE_ON": "AEC مُشغل",
|
||||
"DOWNLOAD_ASSETS_FAILED": "فشل في تنزيل الموارد",
|
||||
"LOADING_ASSETS": "جاري تحميل الموارد...",
|
||||
"PLEASE_WAIT": "يرجى الانتظار...",
|
||||
"FOUND_NEW_ASSETS": "تم العثور على موارد جديدة: %s",
|
||||
"HELLO_MY_FRIEND": "مرحباً، صديقي!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "cs-CZ"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Varování",
|
||||
"INFO": "Informace",
|
||||
"ERROR": "Chyba",
|
||||
"VERSION": "Verze ",
|
||||
"LOADING_PROTOCOL": "Připojování k serveru...",
|
||||
"INITIALIZING": "Inicializace...",
|
||||
"PIN_ERROR": "Prosím vložte SIM kartu",
|
||||
"REG_ERROR": "Nelze se připojit k síti, zkontrolujte stav datové karty",
|
||||
"DETECTING_MODULE": "Detekce modulu...",
|
||||
"REGISTERING_NETWORK": "Čekání na síť...",
|
||||
"CHECKING_NEW_VERSION": "Kontrola nové verze...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Kontrola nové verze selhala, opakování za %d sekund: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Přepínání na Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Přepínání na 4G...",
|
||||
"STANDBY": "Pohotovost",
|
||||
"CONNECT_TO": "Připojit k ",
|
||||
"CONNECTING": "Připojování...",
|
||||
"CONNECTED_TO": "Připojeno k ",
|
||||
"LISTENING": "Naslouchání...",
|
||||
"SPEAKING": "Mluvení...",
|
||||
"SERVER_NOT_FOUND": "Hledání dostupné služby",
|
||||
"SERVER_NOT_CONNECTED": "Nelze se připojit ke službě, zkuste to později",
|
||||
"SERVER_TIMEOUT": "Čas odpovědi vypršel",
|
||||
"SERVER_ERROR": "Odeslání selhalo, zkontrolujte síť",
|
||||
"CONNECT_TO_HOTSPOT": "Připojte telefon k hotspotu ",
|
||||
"ACCESS_VIA_BROWSER": ",přístup přes prohlížeč ",
|
||||
"WIFI_CONFIG_MODE": "Režim konfigurace sítě",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Vstup do režimu konfigurace sítě...",
|
||||
"SCANNING_WIFI": "Skenování Wi-Fi...",
|
||||
"NEW_VERSION": "Nová verze ",
|
||||
"OTA_UPGRADE": "OTA upgrade",
|
||||
"UPGRADING": "Aktualizace systému...",
|
||||
"UPGRADE_FAILED": "Upgrade selhal",
|
||||
"ACTIVATION": "Aktivace zařízení",
|
||||
"BATTERY_LOW": "Slabá baterie",
|
||||
"BATTERY_CHARGING": "Nabíjení",
|
||||
"BATTERY_FULL": "Baterie plná",
|
||||
"BATTERY_NEED_CHARGE": "Slabá baterie, prosím nabijte",
|
||||
"VOLUME": "Hlasitost ",
|
||||
"MUTED": "Ztlumeno",
|
||||
"MAX_VOLUME": "Maximální hlasitost",
|
||||
"RTC_MODE_OFF": "AEC vypnuto",
|
||||
"RTC_MODE_ON": "AEC zapnuto",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Nepodařilo se stáhnout prostředky",
|
||||
"LOADING_ASSETS": "Načítání prostředků...",
|
||||
"PLEASE_WAIT": "Prosím čekejte...",
|
||||
"FOUND_NEW_ASSETS": "Nalezeny nové prostředky: %s",
|
||||
"HELLO_MY_FRIEND": "Ahoj, můj příteli!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "cs-CZ"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Varování",
|
||||
"INFO": "Informace",
|
||||
"ERROR": "Chyba",
|
||||
"VERSION": "Verze ",
|
||||
"LOADING_PROTOCOL": "Připojování k serveru...",
|
||||
"INITIALIZING": "Inicializace...",
|
||||
"PIN_ERROR": "Prosím vložte SIM kartu",
|
||||
"REG_ERROR": "Nelze se připojit k síti, zkontrolujte stav datové karty",
|
||||
"DETECTING_MODULE": "Detekce modulu...",
|
||||
"REGISTERING_NETWORK": "Čekání na síť...",
|
||||
"CHECKING_NEW_VERSION": "Kontrola nové verze...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Kontrola nové verze selhala, opakování za %d sekund: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Přepínání na Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Přepínání na 4G...",
|
||||
"STANDBY": "Pohotovost",
|
||||
"CONNECT_TO": "Připojit k ",
|
||||
"CONNECTING": "Připojování...",
|
||||
"CONNECTED_TO": "Připojeno k ",
|
||||
"LISTENING": "Naslouchání...",
|
||||
"SPEAKING": "Mluvení...",
|
||||
"SERVER_NOT_FOUND": "Hledání dostupné služby",
|
||||
"SERVER_NOT_CONNECTED": "Nelze se připojit ke službě, zkuste to později",
|
||||
"SERVER_TIMEOUT": "Čas odpovědi vypršel",
|
||||
"SERVER_ERROR": "Odeslání selhalo, zkontrolujte síť",
|
||||
"CONNECT_TO_HOTSPOT": "Připojte telefon k hotspotu ",
|
||||
"ACCESS_VIA_BROWSER": ",přístup přes prohlížeč ",
|
||||
"WIFI_CONFIG_MODE": "Režim konfigurace sítě",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Vstup do režimu konfigurace sítě...",
|
||||
"SCANNING_WIFI": "Skenování Wi-Fi...",
|
||||
"NEW_VERSION": "Nová verze ",
|
||||
"OTA_UPGRADE": "OTA upgrade",
|
||||
"UPGRADING": "Aktualizace systému...",
|
||||
"UPGRADE_FAILED": "Upgrade selhal",
|
||||
"ACTIVATION": "Aktivace zařízení",
|
||||
"BATTERY_LOW": "Slabá baterie",
|
||||
"BATTERY_CHARGING": "Nabíjení",
|
||||
"BATTERY_FULL": "Baterie plná",
|
||||
"BATTERY_NEED_CHARGE": "Slabá baterie, prosím nabijte",
|
||||
"VOLUME": "Hlasitost ",
|
||||
"MUTED": "Ztlumeno",
|
||||
"MAX_VOLUME": "Maximální hlasitost",
|
||||
"RTC_MODE_OFF": "AEC vypnuto",
|
||||
"RTC_MODE_ON": "AEC zapnuto",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Nepodařilo se stáhnout prostředky",
|
||||
"LOADING_ASSETS": "Načítání prostředků...",
|
||||
"PLEASE_WAIT": "Prosím čekejte...",
|
||||
"FOUND_NEW_ASSETS": "Nalezeny nové prostředky: %s",
|
||||
"HELLO_MY_FRIEND": "Ahoj, můj příteli!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "de-DE"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Warnung",
|
||||
"INFO": "Information",
|
||||
"ERROR": "Fehler",
|
||||
"VERSION": "Version ",
|
||||
"LOADING_PROTOCOL": "Verbindung zum Server...",
|
||||
"INITIALIZING": "Initialisierung...",
|
||||
"PIN_ERROR": "Bitte SIM-Karte einlegen",
|
||||
"REG_ERROR": "Netzwerkverbindung fehlgeschlagen, bitte Datenkartenstatus prüfen",
|
||||
"DETECTING_MODULE": "Modul erkennen...",
|
||||
"REGISTERING_NETWORK": "Auf Netzwerk warten...",
|
||||
"CHECKING_NEW_VERSION": "Neue Version prüfen...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Neue Version prüfen fehlgeschlagen, Wiederholung in %d Sekunden: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Zu Wi-Fi wechseln...",
|
||||
"SWITCH_TO_4G_NETWORK": "Zu 4G wechseln...",
|
||||
"STANDBY": "Bereitschaft",
|
||||
"CONNECT_TO": "Verbinden zu ",
|
||||
"CONNECTING": "Verbindung wird hergestellt...",
|
||||
"CONNECTED_TO": "Verbunden mit ",
|
||||
"LISTENING": "Zuhören...",
|
||||
"SPEAKING": "Sprechen...",
|
||||
"SERVER_NOT_FOUND": "Verfügbaren Service suchen",
|
||||
"SERVER_NOT_CONNECTED": "Service-Verbindung fehlgeschlagen, bitte später versuchen",
|
||||
"SERVER_TIMEOUT": "Antwort-Timeout",
|
||||
"SERVER_ERROR": "Senden fehlgeschlagen, bitte Netzwerk prüfen",
|
||||
"CONNECT_TO_HOTSPOT": "Handy mit Hotspot verbinden ",
|
||||
"ACCESS_VIA_BROWSER": ",Browser öffnen ",
|
||||
"WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus eingeben...",
|
||||
"SCANNING_WIFI": "Wi-Fi scannen...",
|
||||
"NEW_VERSION": "Neue Version ",
|
||||
"OTA_UPGRADE": "OTA-Upgrade",
|
||||
"UPGRADING": "System wird aktualisiert...",
|
||||
"UPGRADE_FAILED": "Upgrade fehlgeschlagen",
|
||||
"ACTIVATION": "Gerät aktivieren",
|
||||
"BATTERY_LOW": "Niedriger Batteriestand",
|
||||
"BATTERY_CHARGING": "Wird geladen",
|
||||
"BATTERY_FULL": "Batterie voll",
|
||||
"BATTERY_NEED_CHARGE": "Niedriger Batteriestand, bitte aufladen",
|
||||
"VOLUME": "Lautstärke ",
|
||||
"MUTED": "Stummgeschaltet",
|
||||
"MAX_VOLUME": "Maximale Lautstärke",
|
||||
"RTC_MODE_OFF": "AEC aus",
|
||||
"RTC_MODE_ON": "AEC ein",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Fehler beim Herunterladen der Ressourcen",
|
||||
"LOADING_ASSETS": "Ressourcen werden geladen...",
|
||||
"PLEASE_WAIT": "Bitte warten...",
|
||||
"FOUND_NEW_ASSETS": "Neue Ressourcen gefunden: %s",
|
||||
"HELLO_MY_FRIEND": "Hallo, mein Freund!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "de-DE"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Warnung",
|
||||
"INFO": "Information",
|
||||
"ERROR": "Fehler",
|
||||
"VERSION": "Version ",
|
||||
"LOADING_PROTOCOL": "Verbindung zum Server...",
|
||||
"INITIALIZING": "Initialisierung...",
|
||||
"PIN_ERROR": "Bitte SIM-Karte einlegen",
|
||||
"REG_ERROR": "Netzwerkverbindung fehlgeschlagen, bitte Datenkartenstatus prüfen",
|
||||
"DETECTING_MODULE": "Modul erkennen...",
|
||||
"REGISTERING_NETWORK": "Auf Netzwerk warten...",
|
||||
"CHECKING_NEW_VERSION": "Neue Version prüfen...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Neue Version prüfen fehlgeschlagen, Wiederholung in %d Sekunden: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Zu Wi-Fi wechseln...",
|
||||
"SWITCH_TO_4G_NETWORK": "Zu 4G wechseln...",
|
||||
"STANDBY": "Bereitschaft",
|
||||
"CONNECT_TO": "Verbinden zu ",
|
||||
"CONNECTING": "Verbindung wird hergestellt...",
|
||||
"CONNECTED_TO": "Verbunden mit ",
|
||||
"LISTENING": "Zuhören...",
|
||||
"SPEAKING": "Sprechen...",
|
||||
"SERVER_NOT_FOUND": "Verfügbaren Service suchen",
|
||||
"SERVER_NOT_CONNECTED": "Service-Verbindung fehlgeschlagen, bitte später versuchen",
|
||||
"SERVER_TIMEOUT": "Antwort-Timeout",
|
||||
"SERVER_ERROR": "Senden fehlgeschlagen, bitte Netzwerk prüfen",
|
||||
"CONNECT_TO_HOTSPOT": "Handy mit Hotspot verbinden ",
|
||||
"ACCESS_VIA_BROWSER": ",Browser öffnen ",
|
||||
"WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus eingeben...",
|
||||
"SCANNING_WIFI": "Wi-Fi scannen...",
|
||||
"NEW_VERSION": "Neue Version ",
|
||||
"OTA_UPGRADE": "OTA-Upgrade",
|
||||
"UPGRADING": "System wird aktualisiert...",
|
||||
"UPGRADE_FAILED": "Upgrade fehlgeschlagen",
|
||||
"ACTIVATION": "Gerät aktivieren",
|
||||
"BATTERY_LOW": "Niedriger Batteriestand",
|
||||
"BATTERY_CHARGING": "Wird geladen",
|
||||
"BATTERY_FULL": "Batterie voll",
|
||||
"BATTERY_NEED_CHARGE": "Niedriger Batteriestand, bitte aufladen",
|
||||
"VOLUME": "Lautstärke ",
|
||||
"MUTED": "Stummgeschaltet",
|
||||
"MAX_VOLUME": "Maximale Lautstärke",
|
||||
"RTC_MODE_OFF": "AEC aus",
|
||||
"RTC_MODE_ON": "AEC ein",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Fehler beim Herunterladen der Ressourcen",
|
||||
"LOADING_ASSETS": "Ressourcen werden geladen...",
|
||||
"PLEASE_WAIT": "Bitte warten...",
|
||||
"FOUND_NEW_ASSETS": "Neue Ressourcen gefunden: %s",
|
||||
"HELLO_MY_FRIEND": "Hallo, mein Freund!"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,56 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "en-US"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Warning",
|
||||
"INFO": "Information",
|
||||
"ERROR": "Error",
|
||||
"VERSION": "Ver ",
|
||||
"LOADING_PROTOCOL": "Logging in...",
|
||||
"INITIALIZING": "Initializing...",
|
||||
"PIN_ERROR": "Please insert SIM card",
|
||||
"REG_ERROR": "Unable to access network, please check SIM card status",
|
||||
"DETECTING_MODULE": "Detecting module...",
|
||||
"REGISTERING_NETWORK": "Waiting for network...",
|
||||
"CHECKING_NEW_VERSION": "Checking for new version...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Check for new version failed, will retry in %d seconds: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Switching to Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Switching to 4G...",
|
||||
"STANDBY": "Standby",
|
||||
"CONNECT_TO": "Connect to ",
|
||||
"CONNECTING": "Connecting...",
|
||||
"CONNECTION_SUCCESSFUL": "Connection Successful",
|
||||
"CONNECTED_TO": "Connected to ",
|
||||
"LISTENING": "Listening...",
|
||||
"SPEAKING": "Speaking...",
|
||||
"SERVER_NOT_FOUND": "Looking for available service",
|
||||
"SERVER_NOT_CONNECTED": "Unable to connect to service, please try again later",
|
||||
"SERVER_TIMEOUT": "Waiting for response timeout",
|
||||
"SERVER_ERROR": "Sending failed, please check the network",
|
||||
"CONNECT_TO_HOTSPOT": "Hotspot: ",
|
||||
"ACCESS_VIA_BROWSER": " Config URL: ",
|
||||
"WIFI_CONFIG_MODE": "Wi-Fi Configuration Mode",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entering Wi-Fi configuration mode...",
|
||||
"SCANNING_WIFI": "Scanning Wi-Fi...",
|
||||
"NEW_VERSION": "New version ",
|
||||
"OTA_UPGRADE": "OTA Upgrade",
|
||||
"UPGRADING": "System is upgrading...",
|
||||
"UPGRADE_FAILED": "Upgrade failed",
|
||||
"ACTIVATION": "Activation",
|
||||
"BATTERY_LOW": "Low battery",
|
||||
"BATTERY_CHARGING": "Charging",
|
||||
"BATTERY_FULL": "Battery full",
|
||||
"BATTERY_NEED_CHARGE": "Low battery, please charge",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Muted",
|
||||
"MAX_VOLUME": "Max volume",
|
||||
"RTC_MODE_OFF": "AEC Off",
|
||||
"RTC_MODE_ON": "AEC On",
|
||||
"PLEASE_WAIT": "Please wait...",
|
||||
"FOUND_NEW_ASSETS": "Found new assets: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Failed to download assets",
|
||||
"LOADING_ASSETS": "Loading assets...",
|
||||
"HELLO_MY_FRIEND": "Hello, my friend!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "en-US"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Warning",
|
||||
"INFO": "Information",
|
||||
"ERROR": "Error",
|
||||
"VERSION": "Ver ",
|
||||
"LOADING_PROTOCOL": "Logging in...",
|
||||
"INITIALIZING": "Initializing...",
|
||||
"PIN_ERROR": "Please insert SIM card",
|
||||
"REG_ERROR": "Unable to access network, please check SIM card status",
|
||||
"DETECTING_MODULE": "Detecting module...",
|
||||
"REGISTERING_NETWORK": "Waiting for network...",
|
||||
"CHECKING_NEW_VERSION": "Checking for new version...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Check for new version failed, will retry in %d seconds: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Switching to Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Switching to 4G...",
|
||||
"STANDBY": "Standby",
|
||||
"CONNECT_TO": "Connect to ",
|
||||
"CONNECTING": "Connecting...",
|
||||
"CONNECTION_SUCCESSFUL": "Connection Successful",
|
||||
"CONNECTED_TO": "Connected to ",
|
||||
"LISTENING": "Listening...",
|
||||
"SPEAKING": "Speaking...",
|
||||
"SERVER_NOT_FOUND": "Looking for available service",
|
||||
"SERVER_NOT_CONNECTED": "Unable to connect to service, please try again later",
|
||||
"SERVER_TIMEOUT": "Waiting for response timeout",
|
||||
"SERVER_ERROR": "Sending failed, please check the network",
|
||||
"CONNECT_TO_HOTSPOT": "Hotspot: ",
|
||||
"ACCESS_VIA_BROWSER": " Config URL: ",
|
||||
"WIFI_CONFIG_MODE": "Wi-Fi Configuration Mode",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entering Wi-Fi configuration mode...",
|
||||
"SCANNING_WIFI": "Scanning Wi-Fi...",
|
||||
"NEW_VERSION": "New version ",
|
||||
"OTA_UPGRADE": "OTA Upgrade",
|
||||
"UPGRADING": "System is upgrading...",
|
||||
"UPGRADE_FAILED": "Upgrade failed",
|
||||
"ACTIVATION": "Activation",
|
||||
"BATTERY_LOW": "Low battery",
|
||||
"BATTERY_CHARGING": "Charging",
|
||||
"BATTERY_FULL": "Battery full",
|
||||
"BATTERY_NEED_CHARGE": "Low battery, please charge",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Muted",
|
||||
"MAX_VOLUME": "Max volume",
|
||||
"RTC_MODE_OFF": "AEC Off",
|
||||
"RTC_MODE_ON": "AEC On",
|
||||
"PLEASE_WAIT": "Please wait...",
|
||||
"FOUND_NEW_ASSETS": "Found new assets: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Failed to download assets",
|
||||
"LOADING_ASSETS": "Loading assets...",
|
||||
"HELLO_MY_FRIEND": "Hello, my friend!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "es-ES"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Advertencia",
|
||||
"INFO": "Información",
|
||||
"ERROR": "Error",
|
||||
"VERSION": "Versión ",
|
||||
"LOADING_PROTOCOL": "Conectando al servidor...",
|
||||
"INITIALIZING": "Inicializando...",
|
||||
"PIN_ERROR": "Por favor inserte la tarjeta SIM",
|
||||
"REG_ERROR": "No se puede acceder a la red, verifique el estado de la tarjeta de datos",
|
||||
"DETECTING_MODULE": "Detectando módulo...",
|
||||
"REGISTERING_NETWORK": "Esperando red...",
|
||||
"CHECKING_NEW_VERSION": "Verificando nueva versión...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Error al verificar nueva versión, reintentando en %d segundos: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Cambiando a Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Cambiando a 4G...",
|
||||
"STANDBY": "En espera",
|
||||
"CONNECT_TO": "Conectar a ",
|
||||
"CONNECTING": "Conectando...",
|
||||
"CONNECTED_TO": "Conectado a ",
|
||||
"LISTENING": "Escuchando...",
|
||||
"SPEAKING": "Hablando...",
|
||||
"SERVER_NOT_FOUND": "Buscando servicio disponible",
|
||||
"SERVER_NOT_CONNECTED": "No se puede conectar al servicio, inténtelo más tarde",
|
||||
"SERVER_TIMEOUT": "Tiempo de espera agotado",
|
||||
"SERVER_ERROR": "Error de envío, verifique la red",
|
||||
"CONNECT_TO_HOTSPOT": "Conectar teléfono al punto de acceso ",
|
||||
"ACCESS_VIA_BROWSER": ",acceder mediante navegador ",
|
||||
"WIFI_CONFIG_MODE": "Modo configuración de red",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entrando en modo configuración de red...",
|
||||
"SCANNING_WIFI": "Escaneando Wi-Fi...",
|
||||
"NEW_VERSION": "Nueva versión ",
|
||||
"OTA_UPGRADE": "Actualización OTA",
|
||||
"UPGRADING": "Actualizando sistema...",
|
||||
"UPGRADE_FAILED": "Actualización fallida",
|
||||
"ACTIVATION": "Activación del dispositivo",
|
||||
"BATTERY_LOW": "Batería baja",
|
||||
"BATTERY_CHARGING": "Cargando",
|
||||
"BATTERY_FULL": "Batería llena",
|
||||
"BATTERY_NEED_CHARGE": "Batería baja, por favor cargar",
|
||||
"VOLUME": "Volumen ",
|
||||
"MUTED": "Silenciado",
|
||||
"MAX_VOLUME": "Volumen máximo",
|
||||
"RTC_MODE_OFF": "AEC desactivado",
|
||||
"RTC_MODE_ON": "AEC activado",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Error al descargar recursos",
|
||||
"LOADING_ASSETS": "Cargando recursos...",
|
||||
"PLEASE_WAIT": "Por favor espere...",
|
||||
"FOUND_NEW_ASSETS": "Encontrados nuevos recursos: %s",
|
||||
"HELLO_MY_FRIEND": "¡Hola, mi amigo!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "es-ES"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Advertencia",
|
||||
"INFO": "Información",
|
||||
"ERROR": "Error",
|
||||
"VERSION": "Versión ",
|
||||
"LOADING_PROTOCOL": "Conectando al servidor...",
|
||||
"INITIALIZING": "Inicializando...",
|
||||
"PIN_ERROR": "Por favor inserte la tarjeta SIM",
|
||||
"REG_ERROR": "No se puede acceder a la red, verifique el estado de la tarjeta de datos",
|
||||
"DETECTING_MODULE": "Detectando módulo...",
|
||||
"REGISTERING_NETWORK": "Esperando red...",
|
||||
"CHECKING_NEW_VERSION": "Verificando nueva versión...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Error al verificar nueva versión, reintentando en %d segundos: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Cambiando a Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Cambiando a 4G...",
|
||||
"STANDBY": "En espera",
|
||||
"CONNECT_TO": "Conectar a ",
|
||||
"CONNECTING": "Conectando...",
|
||||
"CONNECTED_TO": "Conectado a ",
|
||||
"LISTENING": "Escuchando...",
|
||||
"SPEAKING": "Hablando...",
|
||||
"SERVER_NOT_FOUND": "Buscando servicio disponible",
|
||||
"SERVER_NOT_CONNECTED": "No se puede conectar al servicio, inténtelo más tarde",
|
||||
"SERVER_TIMEOUT": "Tiempo de espera agotado",
|
||||
"SERVER_ERROR": "Error de envío, verifique la red",
|
||||
"CONNECT_TO_HOTSPOT": "Conectar teléfono al punto de acceso ",
|
||||
"ACCESS_VIA_BROWSER": ",acceder mediante navegador ",
|
||||
"WIFI_CONFIG_MODE": "Modo configuración de red",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entrando en modo configuración de red...",
|
||||
"SCANNING_WIFI": "Escaneando Wi-Fi...",
|
||||
"NEW_VERSION": "Nueva versión ",
|
||||
"OTA_UPGRADE": "Actualización OTA",
|
||||
"UPGRADING": "Actualizando sistema...",
|
||||
"UPGRADE_FAILED": "Actualización fallida",
|
||||
"ACTIVATION": "Activación del dispositivo",
|
||||
"BATTERY_LOW": "Batería baja",
|
||||
"BATTERY_CHARGING": "Cargando",
|
||||
"BATTERY_FULL": "Batería llena",
|
||||
"BATTERY_NEED_CHARGE": "Batería baja, por favor cargar",
|
||||
"VOLUME": "Volumen ",
|
||||
"MUTED": "Silenciado",
|
||||
"MAX_VOLUME": "Volumen máximo",
|
||||
"RTC_MODE_OFF": "AEC desactivado",
|
||||
"RTC_MODE_ON": "AEC activado",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Error al descargar recursos",
|
||||
"LOADING_ASSETS": "Cargando recursos...",
|
||||
"PLEASE_WAIT": "Por favor espere...",
|
||||
"FOUND_NEW_ASSETS": "Encontrados nuevos recursos: %s",
|
||||
"HELLO_MY_FRIEND": "¡Hola, mi amigo!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "fi-FI"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Varoitus",
|
||||
"INFO": "Tieto",
|
||||
"ERROR": "Virhe",
|
||||
"VERSION": "Versio ",
|
||||
"LOADING_PROTOCOL": "Yhdistetään palvelimeen...",
|
||||
"INITIALIZING": "Alustetaan...",
|
||||
"PIN_ERROR": "Ole hyvä ja aseta SIM-kortti",
|
||||
"REG_ERROR": "Ei voi muodostaa yhteyttä verkkoon, tarkista datakortin tila",
|
||||
"DETECTING_MODULE": "Tunnistetaan moduuli...",
|
||||
"REGISTERING_NETWORK": "Odotetaan verkkoa...",
|
||||
"CHECKING_NEW_VERSION": "Tarkistetaan uutta versiota...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Uuden version tarkistus epäonnistui, yritetään uudelleen %d sekunnin kuluttua: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Vaihdetaan Wi-Fi:hin...",
|
||||
"SWITCH_TO_4G_NETWORK": "Vaihdetaan 4G:hen...",
|
||||
"STANDBY": "Valmiustila",
|
||||
"CONNECT_TO": "Yhdistä ",
|
||||
"CONNECTING": "Yhdistetään...",
|
||||
"CONNECTED_TO": "Yhdistetty ",
|
||||
"LISTENING": "Kuunnellaan...",
|
||||
"SPEAKING": "Puhutaan...",
|
||||
"SERVER_NOT_FOUND": "Etsitään käytettävissä olevaa palvelua",
|
||||
"SERVER_NOT_CONNECTED": "Ei voi yhdistää palveluun, yritä myöhemmin",
|
||||
"SERVER_TIMEOUT": "Vastauksen aikakatkaisu",
|
||||
"SERVER_ERROR": "Lähetys epäonnistui, tarkista verkko",
|
||||
"CONNECT_TO_HOTSPOT": "Yhdistä puhelin hotspottiin ",
|
||||
"ACCESS_VIA_BROWSER": ",pääsy selaimen kautta ",
|
||||
"WIFI_CONFIG_MODE": "Verkon konfigurointitila",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Siirrytään verkon konfigurointitilaan...",
|
||||
"SCANNING_WIFI": "Skannataan Wi-Fi...",
|
||||
"NEW_VERSION": "Uusi versio ",
|
||||
"OTA_UPGRADE": "OTA-päivitys",
|
||||
"UPGRADING": "Päivitetään järjestelmää...",
|
||||
"UPGRADE_FAILED": "Päivitys epäonnistui",
|
||||
"ACTIVATION": "Laitteen aktivointi",
|
||||
"BATTERY_LOW": "Akku vähissä",
|
||||
"BATTERY_CHARGING": "Ladataan",
|
||||
"BATTERY_FULL": "Akku täynnä",
|
||||
"BATTERY_NEED_CHARGE": "Akku vähissä, ole hyvä ja lataa",
|
||||
"VOLUME": "Äänenvoimakkuus ",
|
||||
"MUTED": "Mykistetty",
|
||||
"MAX_VOLUME": "Maksimi äänenvoimakkuus",
|
||||
"RTC_MODE_OFF": "AEC pois päältä",
|
||||
"RTC_MODE_ON": "AEC päällä",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Resurssien lataaminen epäonnistui",
|
||||
"LOADING_ASSETS": "Ladataan resursseja...",
|
||||
"PLEASE_WAIT": "Odota hetki...",
|
||||
"FOUND_NEW_ASSETS": "Löydetty uusia resursseja: %s",
|
||||
"HELLO_MY_FRIEND": "Hei, ystäväni!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "fi-FI"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Varoitus",
|
||||
"INFO": "Tieto",
|
||||
"ERROR": "Virhe",
|
||||
"VERSION": "Versio ",
|
||||
"LOADING_PROTOCOL": "Yhdistetään palvelimeen...",
|
||||
"INITIALIZING": "Alustetaan...",
|
||||
"PIN_ERROR": "Ole hyvä ja aseta SIM-kortti",
|
||||
"REG_ERROR": "Ei voi muodostaa yhteyttä verkkoon, tarkista datakortin tila",
|
||||
"DETECTING_MODULE": "Tunnistetaan moduuli...",
|
||||
"REGISTERING_NETWORK": "Odotetaan verkkoa...",
|
||||
"CHECKING_NEW_VERSION": "Tarkistetaan uutta versiota...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Uuden version tarkistus epäonnistui, yritetään uudelleen %d sekunnin kuluttua: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Vaihdetaan Wi-Fi:hin...",
|
||||
"SWITCH_TO_4G_NETWORK": "Vaihdetaan 4G:hen...",
|
||||
"STANDBY": "Valmiustila",
|
||||
"CONNECT_TO": "Yhdistä ",
|
||||
"CONNECTING": "Yhdistetään...",
|
||||
"CONNECTED_TO": "Yhdistetty ",
|
||||
"LISTENING": "Kuunnellaan...",
|
||||
"SPEAKING": "Puhutaan...",
|
||||
"SERVER_NOT_FOUND": "Etsitään käytettävissä olevaa palvelua",
|
||||
"SERVER_NOT_CONNECTED": "Ei voi yhdistää palveluun, yritä myöhemmin",
|
||||
"SERVER_TIMEOUT": "Vastauksen aikakatkaisu",
|
||||
"SERVER_ERROR": "Lähetys epäonnistui, tarkista verkko",
|
||||
"CONNECT_TO_HOTSPOT": "Yhdistä puhelin hotspottiin ",
|
||||
"ACCESS_VIA_BROWSER": ",pääsy selaimen kautta ",
|
||||
"WIFI_CONFIG_MODE": "Verkon konfigurointitila",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Siirrytään verkon konfigurointitilaan...",
|
||||
"SCANNING_WIFI": "Skannataan Wi-Fi...",
|
||||
"NEW_VERSION": "Uusi versio ",
|
||||
"OTA_UPGRADE": "OTA-päivitys",
|
||||
"UPGRADING": "Päivitetään järjestelmää...",
|
||||
"UPGRADE_FAILED": "Päivitys epäonnistui",
|
||||
"ACTIVATION": "Laitteen aktivointi",
|
||||
"BATTERY_LOW": "Akku vähissä",
|
||||
"BATTERY_CHARGING": "Ladataan",
|
||||
"BATTERY_FULL": "Akku täynnä",
|
||||
"BATTERY_NEED_CHARGE": "Akku vähissä, ole hyvä ja lataa",
|
||||
"VOLUME": "Äänenvoimakkuus ",
|
||||
"MUTED": "Mykistetty",
|
||||
"MAX_VOLUME": "Maksimi äänenvoimakkuus",
|
||||
"RTC_MODE_OFF": "AEC pois päältä",
|
||||
"RTC_MODE_ON": "AEC päällä",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Resurssien lataaminen epäonnistui",
|
||||
"LOADING_ASSETS": "Ladataan resursseja...",
|
||||
"PLEASE_WAIT": "Odota hetki...",
|
||||
"FOUND_NEW_ASSETS": "Löydetty uusia resursseja: %s",
|
||||
"HELLO_MY_FRIEND": "Hei, ystäväni!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "fr-FR"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Avertissement",
|
||||
"INFO": "Information",
|
||||
"ERROR": "Erreur",
|
||||
"VERSION": "Version ",
|
||||
"LOADING_PROTOCOL": "Connexion au serveur...",
|
||||
"INITIALIZING": "Initialisation...",
|
||||
"PIN_ERROR": "Veuillez insérer la carte SIM",
|
||||
"REG_ERROR": "Impossible d'accéder au réseau, veuillez vérifier l'état de la carte de données",
|
||||
"DETECTING_MODULE": "Détection du module...",
|
||||
"REGISTERING_NETWORK": "En attente du réseau...",
|
||||
"CHECKING_NEW_VERSION": "Vérification de nouvelle version...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Échec de vérification de nouvelle version, nouvelle tentative dans %d secondes : %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Basculer vers Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Basculer vers 4G...",
|
||||
"STANDBY": "En attente",
|
||||
"CONNECT_TO": "Se connecter à ",
|
||||
"CONNECTING": "Connexion en cours...",
|
||||
"CONNECTED_TO": "Connecté à ",
|
||||
"LISTENING": "Écoute...",
|
||||
"SPEAKING": "Parole...",
|
||||
"SERVER_NOT_FOUND": "Recherche d'un service disponible",
|
||||
"SERVER_NOT_CONNECTED": "Impossible de se connecter au service, veuillez réessayer plus tard",
|
||||
"SERVER_TIMEOUT": "Délai d'attente de réponse",
|
||||
"SERVER_ERROR": "Échec d'envoi, veuillez vérifier le réseau",
|
||||
"CONNECT_TO_HOTSPOT": "Connecter le téléphone au point d'accès ",
|
||||
"ACCESS_VIA_BROWSER": ",accéder via le navigateur ",
|
||||
"WIFI_CONFIG_MODE": "Mode configuration réseau",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entrer en mode configuration réseau...",
|
||||
"SCANNING_WIFI": "Scan Wi-Fi...",
|
||||
"NEW_VERSION": "Nouvelle version ",
|
||||
"OTA_UPGRADE": "Mise à jour OTA",
|
||||
"UPGRADING": "Mise à jour du système...",
|
||||
"UPGRADE_FAILED": "Échec de mise à jour",
|
||||
"ACTIVATION": "Activation de l'appareil",
|
||||
"BATTERY_LOW": "Batterie faible",
|
||||
"BATTERY_CHARGING": "En charge",
|
||||
"BATTERY_FULL": "Batterie pleine",
|
||||
"BATTERY_NEED_CHARGE": "Batterie faible, veuillez charger",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Muet",
|
||||
"MAX_VOLUME": "Volume maximum",
|
||||
"RTC_MODE_OFF": "AEC désactivé",
|
||||
"RTC_MODE_ON": "AEC activé",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Échec du téléchargement des ressources",
|
||||
"LOADING_ASSETS": "Chargement des ressources...",
|
||||
"PLEASE_WAIT": "Veuillez patienter...",
|
||||
"FOUND_NEW_ASSETS": "Nouvelles ressources trouvées: %s",
|
||||
"HELLO_MY_FRIEND": "Bonjour, mon ami !"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "fr-FR"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Avertissement",
|
||||
"INFO": "Information",
|
||||
"ERROR": "Erreur",
|
||||
"VERSION": "Version ",
|
||||
"LOADING_PROTOCOL": "Connexion au serveur...",
|
||||
"INITIALIZING": "Initialisation...",
|
||||
"PIN_ERROR": "Veuillez insérer la carte SIM",
|
||||
"REG_ERROR": "Impossible d'accéder au réseau, veuillez vérifier l'état de la carte de données",
|
||||
"DETECTING_MODULE": "Détection du module...",
|
||||
"REGISTERING_NETWORK": "En attente du réseau...",
|
||||
"CHECKING_NEW_VERSION": "Vérification de nouvelle version...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Échec de vérification de nouvelle version, nouvelle tentative dans %d secondes : %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Basculer vers Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Basculer vers 4G...",
|
||||
"STANDBY": "En attente",
|
||||
"CONNECT_TO": "Se connecter à ",
|
||||
"CONNECTING": "Connexion en cours...",
|
||||
"CONNECTED_TO": "Connecté à ",
|
||||
"LISTENING": "Écoute...",
|
||||
"SPEAKING": "Parole...",
|
||||
"SERVER_NOT_FOUND": "Recherche d'un service disponible",
|
||||
"SERVER_NOT_CONNECTED": "Impossible de se connecter au service, veuillez réessayer plus tard",
|
||||
"SERVER_TIMEOUT": "Délai d'attente de réponse",
|
||||
"SERVER_ERROR": "Échec d'envoi, veuillez vérifier le réseau",
|
||||
"CONNECT_TO_HOTSPOT": "Connecter le téléphone au point d'accès ",
|
||||
"ACCESS_VIA_BROWSER": ",accéder via le navigateur ",
|
||||
"WIFI_CONFIG_MODE": "Mode configuration réseau",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entrer en mode configuration réseau...",
|
||||
"SCANNING_WIFI": "Scan Wi-Fi...",
|
||||
"NEW_VERSION": "Nouvelle version ",
|
||||
"OTA_UPGRADE": "Mise à jour OTA",
|
||||
"UPGRADING": "Mise à jour du système...",
|
||||
"UPGRADE_FAILED": "Échec de mise à jour",
|
||||
"ACTIVATION": "Activation de l'appareil",
|
||||
"BATTERY_LOW": "Batterie faible",
|
||||
"BATTERY_CHARGING": "En charge",
|
||||
"BATTERY_FULL": "Batterie pleine",
|
||||
"BATTERY_NEED_CHARGE": "Batterie faible, veuillez charger",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Muet",
|
||||
"MAX_VOLUME": "Volume maximum",
|
||||
"RTC_MODE_OFF": "AEC désactivé",
|
||||
"RTC_MODE_ON": "AEC activé",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Échec du téléchargement des ressources",
|
||||
"LOADING_ASSETS": "Chargement des ressources...",
|
||||
"PLEASE_WAIT": "Veuillez patienter...",
|
||||
"FOUND_NEW_ASSETS": "Nouvelles ressources trouvées: %s",
|
||||
"HELLO_MY_FRIEND": "Bonjour, mon ami !"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "hi-IN"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "चेतावनी",
|
||||
"INFO": "जानकारी",
|
||||
"ERROR": "त्रुटि",
|
||||
"VERSION": "संस्करण ",
|
||||
"LOADING_PROTOCOL": "सर्वर से कनेक्ट हो रहे हैं...",
|
||||
"INITIALIZING": "आरंभीकरण...",
|
||||
"PIN_ERROR": "कृपया सिम कार्ड डालें",
|
||||
"REG_ERROR": "नेटवर्क तक पहुंच नहीं हो सकती, कृपया डेटा कार्ड स्थिति जांचें",
|
||||
"DETECTING_MODULE": "मॉड्यूल का पता लगाया जा रहा है...",
|
||||
"REGISTERING_NETWORK": "नेटवर्क की प्रतीक्षा...",
|
||||
"CHECKING_NEW_VERSION": "नया संस्करण जाँच रहे हैं...",
|
||||
"CHECK_NEW_VERSION_FAILED": "नया संस्करण जाँचना असफल, %d सेकंड में पुनः प्रयास: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fi पर स्विच कर रहे हैं...",
|
||||
"SWITCH_TO_4G_NETWORK": "4G पर स्विच कर रहे हैं...",
|
||||
"STANDBY": "स्टैंडबाय",
|
||||
"CONNECT_TO": "कनेक्ट करें ",
|
||||
"CONNECTING": "कनेक्ट हो रहे हैं...",
|
||||
"CONNECTED_TO": "कनेक्ट हो गए ",
|
||||
"LISTENING": "सुन रहे हैं...",
|
||||
"SPEAKING": "बोल रहे हैं...",
|
||||
"SERVER_NOT_FOUND": "उपलब्ध सेवा खोज रहे हैं",
|
||||
"SERVER_NOT_CONNECTED": "सेवा से कनेक्ट नहीं हो सकते, कृपया बाद में कोशिश करें",
|
||||
"SERVER_TIMEOUT": "प्रतिक्रिया का समय समाप्त",
|
||||
"SERVER_ERROR": "भेजना असफल, कृपया नेटवर्क जांचें",
|
||||
"CONNECT_TO_HOTSPOT": "फोन को हॉटस्पॉट से कनेक्ट करें ",
|
||||
"ACCESS_VIA_BROWSER": ",ब्राउज़र के माध्यम से पहुंचें ",
|
||||
"WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड में प्रवेश...",
|
||||
"SCANNING_WIFI": "Wi-Fi स्कैन कर रहे हैं...",
|
||||
"NEW_VERSION": "नया संस्करण ",
|
||||
"OTA_UPGRADE": "OTA अपग्रेड",
|
||||
"UPGRADING": "सिस्टम अपग्रेड हो रहा है...",
|
||||
"UPGRADE_FAILED": "अपग्रेड असफल",
|
||||
"ACTIVATION": "डिवाइस सक्रियण",
|
||||
"BATTERY_LOW": "बैटरी कम",
|
||||
"BATTERY_CHARGING": "चार्ज हो रही है",
|
||||
"BATTERY_FULL": "बैटरी फुल",
|
||||
"BATTERY_NEED_CHARGE": "बैटरी कम है, कृपया चार्ज करें",
|
||||
"VOLUME": "आवाज़ ",
|
||||
"MUTED": "म्यूट",
|
||||
"MAX_VOLUME": "अधिकतम आवाज़",
|
||||
"RTC_MODE_OFF": "AEC बंद",
|
||||
"RTC_MODE_ON": "AEC चालू",
|
||||
"DOWNLOAD_ASSETS_FAILED": "संसाधन डाउनलोड करने में विफल",
|
||||
"LOADING_ASSETS": "संसाधन लोड हो रहे हैं...",
|
||||
"PLEASE_WAIT": "कृपया प्रतीक्षा करें...",
|
||||
"FOUND_NEW_ASSETS": "नए संसाधन मिले: %s",
|
||||
"HELLO_MY_FRIEND": "नमस्ते, मेरे दोस्त!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "hi-IN"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "चेतावनी",
|
||||
"INFO": "जानकारी",
|
||||
"ERROR": "त्रुटि",
|
||||
"VERSION": "संस्करण ",
|
||||
"LOADING_PROTOCOL": "सर्वर से कनेक्ट हो रहे हैं...",
|
||||
"INITIALIZING": "आरंभीकरण...",
|
||||
"PIN_ERROR": "कृपया सिम कार्ड डालें",
|
||||
"REG_ERROR": "नेटवर्क तक पहुंच नहीं हो सकती, कृपया डेटा कार्ड स्थिति जांचें",
|
||||
"DETECTING_MODULE": "मॉड्यूल का पता लगाया जा रहा है...",
|
||||
"REGISTERING_NETWORK": "नेटवर्क की प्रतीक्षा...",
|
||||
"CHECKING_NEW_VERSION": "नया संस्करण जाँच रहे हैं...",
|
||||
"CHECK_NEW_VERSION_FAILED": "नया संस्करण जाँचना असफल, %d सेकंड में पुनः प्रयास: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fi पर स्विच कर रहे हैं...",
|
||||
"SWITCH_TO_4G_NETWORK": "4G पर स्विच कर रहे हैं...",
|
||||
"STANDBY": "स्टैंडबाय",
|
||||
"CONNECT_TO": "कनेक्ट करें ",
|
||||
"CONNECTING": "कनेक्ट हो रहे हैं...",
|
||||
"CONNECTED_TO": "कनेक्ट हो गए ",
|
||||
"LISTENING": "सुन रहे हैं...",
|
||||
"SPEAKING": "बोल रहे हैं...",
|
||||
"SERVER_NOT_FOUND": "उपलब्ध सेवा खोज रहे हैं",
|
||||
"SERVER_NOT_CONNECTED": "सेवा से कनेक्ट नहीं हो सकते, कृपया बाद में कोशिश करें",
|
||||
"SERVER_TIMEOUT": "प्रतिक्रिया का समय समाप्त",
|
||||
"SERVER_ERROR": "भेजना असफल, कृपया नेटवर्क जांचें",
|
||||
"CONNECT_TO_HOTSPOT": "फोन को हॉटस्पॉट से कनेक्ट करें ",
|
||||
"ACCESS_VIA_BROWSER": ",ब्राउज़र के माध्यम से पहुंचें ",
|
||||
"WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "नेटवर्क कॉन्फ़िगरेशन मोड में प्रवेश...",
|
||||
"SCANNING_WIFI": "Wi-Fi स्कैन कर रहे हैं...",
|
||||
"NEW_VERSION": "नया संस्करण ",
|
||||
"OTA_UPGRADE": "OTA अपग्रेड",
|
||||
"UPGRADING": "सिस्टम अपग्रेड हो रहा है...",
|
||||
"UPGRADE_FAILED": "अपग्रेड असफल",
|
||||
"ACTIVATION": "डिवाइस सक्रियण",
|
||||
"BATTERY_LOW": "बैटरी कम",
|
||||
"BATTERY_CHARGING": "चार्ज हो रही है",
|
||||
"BATTERY_FULL": "बैटरी फुल",
|
||||
"BATTERY_NEED_CHARGE": "बैटरी कम है, कृपया चार्ज करें",
|
||||
"VOLUME": "आवाज़ ",
|
||||
"MUTED": "म्यूट",
|
||||
"MAX_VOLUME": "अधिकतम आवाज़",
|
||||
"RTC_MODE_OFF": "AEC बंद",
|
||||
"RTC_MODE_ON": "AEC चालू",
|
||||
"DOWNLOAD_ASSETS_FAILED": "संसाधन डाउनलोड करने में विफल",
|
||||
"LOADING_ASSETS": "संसाधन लोड हो रहे हैं...",
|
||||
"PLEASE_WAIT": "कृपया प्रतीक्षा करें...",
|
||||
"FOUND_NEW_ASSETS": "नए संसाधन मिले: %s",
|
||||
"HELLO_MY_FRIEND": "नमस्ते, मेरे दोस्त!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "id-ID"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Peringatan",
|
||||
"INFO": "Informasi",
|
||||
"ERROR": "Kesalahan",
|
||||
"VERSION": "Versi ",
|
||||
"LOADING_PROTOCOL": "Menghubungkan ke server...",
|
||||
"INITIALIZING": "Menginisialisasi...",
|
||||
"PIN_ERROR": "Silakan masukkan kartu SIM",
|
||||
"REG_ERROR": "Tidak dapat mengakses jaringan, periksa status kartu data",
|
||||
"DETECTING_MODULE": "Mendeteksi modul...",
|
||||
"REGISTERING_NETWORK": "Menunggu jaringan...",
|
||||
"CHECKING_NEW_VERSION": "Memeriksa versi baru...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Pemeriksaan versi baru gagal, mencoba lagi dalam %d detik: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Beralih ke Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Beralih ke 4G...",
|
||||
"STANDBY": "Siaga",
|
||||
"CONNECT_TO": "Hubungkan ke ",
|
||||
"CONNECTING": "Menghubungkan...",
|
||||
"CONNECTED_TO": "Terhubung ke ",
|
||||
"LISTENING": "Mendengarkan...",
|
||||
"SPEAKING": "Berbicara...",
|
||||
"SERVER_NOT_FOUND": "Mencari layanan yang tersedia",
|
||||
"SERVER_NOT_CONNECTED": "Tidak dapat terhubung ke layanan, coba lagi nanti",
|
||||
"SERVER_TIMEOUT": "Waktu respons habis",
|
||||
"SERVER_ERROR": "Pengiriman gagal, periksa jaringan",
|
||||
"CONNECT_TO_HOTSPOT": "Hubungkan ponsel ke hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",akses melalui browser ",
|
||||
"WIFI_CONFIG_MODE": "Mode konfigurasi jaringan",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Memasuki mode konfigurasi jaringan...",
|
||||
"SCANNING_WIFI": "Memindai Wi-Fi...",
|
||||
"NEW_VERSION": "Versi baru ",
|
||||
"OTA_UPGRADE": "Pembaruan OTA",
|
||||
"UPGRADING": "Memperbarui sistem...",
|
||||
"UPGRADE_FAILED": "Pembaruan gagal",
|
||||
"ACTIVATION": "Aktivasi perangkat",
|
||||
"BATTERY_LOW": "Baterai lemah",
|
||||
"BATTERY_CHARGING": "Mengisi",
|
||||
"BATTERY_FULL": "Baterai penuh",
|
||||
"BATTERY_NEED_CHARGE": "Baterai lemah, silakan isi",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Bisu",
|
||||
"MAX_VOLUME": "Volume maksimum",
|
||||
"RTC_MODE_OFF": "AEC mati",
|
||||
"RTC_MODE_ON": "AEC nyala",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Gagal mengunduh aset",
|
||||
"LOADING_ASSETS": "Memuat aset...",
|
||||
"PLEASE_WAIT": "Mohon tunggu...",
|
||||
"FOUND_NEW_ASSETS": "Ditemukan aset baru: %s",
|
||||
"HELLO_MY_FRIEND": "Halo, teman saya!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "id-ID"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Peringatan",
|
||||
"INFO": "Informasi",
|
||||
"ERROR": "Kesalahan",
|
||||
"VERSION": "Versi ",
|
||||
"LOADING_PROTOCOL": "Menghubungkan ke server...",
|
||||
"INITIALIZING": "Menginisialisasi...",
|
||||
"PIN_ERROR": "Silakan masukkan kartu SIM",
|
||||
"REG_ERROR": "Tidak dapat mengakses jaringan, periksa status kartu data",
|
||||
"DETECTING_MODULE": "Mendeteksi modul...",
|
||||
"REGISTERING_NETWORK": "Menunggu jaringan...",
|
||||
"CHECKING_NEW_VERSION": "Memeriksa versi baru...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Pemeriksaan versi baru gagal, mencoba lagi dalam %d detik: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Beralih ke Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Beralih ke 4G...",
|
||||
"STANDBY": "Siaga",
|
||||
"CONNECT_TO": "Hubungkan ke ",
|
||||
"CONNECTING": "Menghubungkan...",
|
||||
"CONNECTED_TO": "Terhubung ke ",
|
||||
"LISTENING": "Mendengarkan...",
|
||||
"SPEAKING": "Berbicara...",
|
||||
"SERVER_NOT_FOUND": "Mencari layanan yang tersedia",
|
||||
"SERVER_NOT_CONNECTED": "Tidak dapat terhubung ke layanan, coba lagi nanti",
|
||||
"SERVER_TIMEOUT": "Waktu respons habis",
|
||||
"SERVER_ERROR": "Pengiriman gagal, periksa jaringan",
|
||||
"CONNECT_TO_HOTSPOT": "Hubungkan ponsel ke hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",akses melalui browser ",
|
||||
"WIFI_CONFIG_MODE": "Mode konfigurasi jaringan",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Memasuki mode konfigurasi jaringan...",
|
||||
"SCANNING_WIFI": "Memindai Wi-Fi...",
|
||||
"NEW_VERSION": "Versi baru ",
|
||||
"OTA_UPGRADE": "Pembaruan OTA",
|
||||
"UPGRADING": "Memperbarui sistem...",
|
||||
"UPGRADE_FAILED": "Pembaruan gagal",
|
||||
"ACTIVATION": "Aktivasi perangkat",
|
||||
"BATTERY_LOW": "Baterai lemah",
|
||||
"BATTERY_CHARGING": "Mengisi",
|
||||
"BATTERY_FULL": "Baterai penuh",
|
||||
"BATTERY_NEED_CHARGE": "Baterai lemah, silakan isi",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Bisu",
|
||||
"MAX_VOLUME": "Volume maksimum",
|
||||
"RTC_MODE_OFF": "AEC mati",
|
||||
"RTC_MODE_ON": "AEC nyala",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Gagal mengunduh aset",
|
||||
"LOADING_ASSETS": "Memuat aset...",
|
||||
"PLEASE_WAIT": "Mohon tunggu...",
|
||||
"FOUND_NEW_ASSETS": "Ditemukan aset baru: %s",
|
||||
"HELLO_MY_FRIEND": "Halo, teman saya!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "it-IT"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Avviso",
|
||||
"INFO": "Informazione",
|
||||
"ERROR": "Errore",
|
||||
"VERSION": "Versione ",
|
||||
"LOADING_PROTOCOL": "Connessione al server...",
|
||||
"INITIALIZING": "Inizializzazione...",
|
||||
"PIN_ERROR": "Inserire la scheda SIM",
|
||||
"REG_ERROR": "Impossibile accedere alla rete, controllare lo stato della scheda dati",
|
||||
"DETECTING_MODULE": "Rilevamento modulo...",
|
||||
"REGISTERING_NETWORK": "In attesa della rete...",
|
||||
"CHECKING_NEW_VERSION": "Controllo nuova versione...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Controllo nuova versione fallito, riprovo tra %d secondi: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Passaggio a Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Passaggio a 4G...",
|
||||
"STANDBY": "In attesa",
|
||||
"CONNECT_TO": "Connetti a ",
|
||||
"CONNECTING": "Connessione...",
|
||||
"CONNECTED_TO": "Connesso a ",
|
||||
"LISTENING": "In ascolto...",
|
||||
"SPEAKING": "Parlando...",
|
||||
"SERVER_NOT_FOUND": "Ricerca servizio disponibile",
|
||||
"SERVER_NOT_CONNECTED": "Impossibile connettersi al servizio, riprovare più tardi",
|
||||
"SERVER_TIMEOUT": "Timeout risposta",
|
||||
"SERVER_ERROR": "Invio fallito, controllare la rete",
|
||||
"CONNECT_TO_HOTSPOT": "Connetti telefono al hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",accedi tramite browser ",
|
||||
"WIFI_CONFIG_MODE": "Modalità configurazione rete",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entrata in modalità configurazione rete...",
|
||||
"SCANNING_WIFI": "Scansione Wi-Fi...",
|
||||
"NEW_VERSION": "Nuova versione ",
|
||||
"OTA_UPGRADE": "Aggiornamento OTA",
|
||||
"UPGRADING": "Aggiornamento sistema...",
|
||||
"UPGRADE_FAILED": "Aggiornamento fallito",
|
||||
"ACTIVATION": "Attivazione dispositivo",
|
||||
"BATTERY_LOW": "Batteria scarica",
|
||||
"BATTERY_CHARGING": "In carica",
|
||||
"BATTERY_FULL": "Batteria piena",
|
||||
"BATTERY_NEED_CHARGE": "Batteria scarica, ricaricare",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Silenziato",
|
||||
"MAX_VOLUME": "Volume massimo",
|
||||
"RTC_MODE_OFF": "AEC disattivato",
|
||||
"RTC_MODE_ON": "AEC attivato",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Impossibile scaricare le risorse",
|
||||
"LOADING_ASSETS": "Caricamento risorse...",
|
||||
"PLEASE_WAIT": "Attendere prego...",
|
||||
"FOUND_NEW_ASSETS": "Trovate nuove risorse: %s",
|
||||
"HELLO_MY_FRIEND": "Ciao, amico mio!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "it-IT"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Avviso",
|
||||
"INFO": "Informazione",
|
||||
"ERROR": "Errore",
|
||||
"VERSION": "Versione ",
|
||||
"LOADING_PROTOCOL": "Connessione al server...",
|
||||
"INITIALIZING": "Inizializzazione...",
|
||||
"PIN_ERROR": "Inserire la scheda SIM",
|
||||
"REG_ERROR": "Impossibile accedere alla rete, controllare lo stato della scheda dati",
|
||||
"DETECTING_MODULE": "Rilevamento modulo...",
|
||||
"REGISTERING_NETWORK": "In attesa della rete...",
|
||||
"CHECKING_NEW_VERSION": "Controllo nuova versione...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Controllo nuova versione fallito, riprovo tra %d secondi: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Passaggio a Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Passaggio a 4G...",
|
||||
"STANDBY": "In attesa",
|
||||
"CONNECT_TO": "Connetti a ",
|
||||
"CONNECTING": "Connessione...",
|
||||
"CONNECTED_TO": "Connesso a ",
|
||||
"LISTENING": "In ascolto...",
|
||||
"SPEAKING": "Parlando...",
|
||||
"SERVER_NOT_FOUND": "Ricerca servizio disponibile",
|
||||
"SERVER_NOT_CONNECTED": "Impossibile connettersi al servizio, riprovare più tardi",
|
||||
"SERVER_TIMEOUT": "Timeout risposta",
|
||||
"SERVER_ERROR": "Invio fallito, controllare la rete",
|
||||
"CONNECT_TO_HOTSPOT": "Connetti telefono al hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",accedi tramite browser ",
|
||||
"WIFI_CONFIG_MODE": "Modalità configurazione rete",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Entrata in modalità configurazione rete...",
|
||||
"SCANNING_WIFI": "Scansione Wi-Fi...",
|
||||
"NEW_VERSION": "Nuova versione ",
|
||||
"OTA_UPGRADE": "Aggiornamento OTA",
|
||||
"UPGRADING": "Aggiornamento sistema...",
|
||||
"UPGRADE_FAILED": "Aggiornamento fallito",
|
||||
"ACTIVATION": "Attivazione dispositivo",
|
||||
"BATTERY_LOW": "Batteria scarica",
|
||||
"BATTERY_CHARGING": "In carica",
|
||||
"BATTERY_FULL": "Batteria piena",
|
||||
"BATTERY_NEED_CHARGE": "Batteria scarica, ricaricare",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Silenziato",
|
||||
"MAX_VOLUME": "Volume massimo",
|
||||
"RTC_MODE_OFF": "AEC disattivato",
|
||||
"RTC_MODE_ON": "AEC attivato",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Impossibile scaricare le risorse",
|
||||
"LOADING_ASSETS": "Caricamento risorse...",
|
||||
"PLEASE_WAIT": "Attendere prego...",
|
||||
"FOUND_NEW_ASSETS": "Trovate nuove risorse: %s",
|
||||
"HELLO_MY_FRIEND": "Ciao, amico mio!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "ja-JP"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "警告",
|
||||
"INFO": "情報",
|
||||
"ERROR": "エラー",
|
||||
"VERSION": "バージョン ",
|
||||
"LOADING_PROTOCOL": "サーバーにログイン中...",
|
||||
"INITIALIZING": "初期化中...",
|
||||
"PIN_ERROR": "SIMカードを挿入してください",
|
||||
"REG_ERROR": "ネットワークに接続できません。ネットワーク状態を確認してください",
|
||||
"DETECTING_MODULE": "モジュールを検出中...",
|
||||
"REGISTERING_NETWORK": "ネットワーク接続待機中...",
|
||||
"CHECKING_NEW_VERSION": "新しいバージョンを確認中...",
|
||||
"CHECK_NEW_VERSION_FAILED": "更新確認に失敗しました。%d 秒後に再試行します: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fiに切り替え中...",
|
||||
"SWITCH_TO_4G_NETWORK": "4Gに切り替え中...",
|
||||
"STANDBY": "待機中",
|
||||
"CONNECT_TO": "接続先 ",
|
||||
"CONNECTING": "接続中...",
|
||||
"CONNECTED_TO": "接続完了 ",
|
||||
"LISTENING": "リスニング中...",
|
||||
"SPEAKING": "話しています...",
|
||||
"SERVER_NOT_FOUND": "利用可能なサーバーを探しています",
|
||||
"SERVER_NOT_CONNECTED": "サーバーに接続できません。後でもう一度お試しください",
|
||||
"SERVER_TIMEOUT": "応答待機時間が終了しました",
|
||||
"SERVER_ERROR": "送信に失敗しました。ネットワークを確認してください",
|
||||
"CONNECT_TO_HOTSPOT": "スマートフォンをWi-Fi ",
|
||||
"ACCESS_VIA_BROWSER": " に接続し、ブラウザでアクセスしてください ",
|
||||
"WIFI_CONFIG_MODE": "ネットワーク設定モード",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "ネットワーク設定中...",
|
||||
"SCANNING_WIFI": "Wi-Fiをスキャン中...",
|
||||
"NEW_VERSION": "新しいバージョン ",
|
||||
"OTA_UPGRADE": "OTAアップグレード",
|
||||
"UPGRADING": "システムをアップグレード中...",
|
||||
"UPGRADE_FAILED": "アップグレード失敗",
|
||||
"ACTIVATION": "デバイスをアクティベート",
|
||||
"BATTERY_LOW": "バッテリーが少なくなっています",
|
||||
"BATTERY_CHARGING": "充電中",
|
||||
"BATTERY_FULL": "バッテリー満タン",
|
||||
"BATTERY_NEED_CHARGE": "バッテリーが低下しています。充電してください",
|
||||
"VOLUME": "音量 ",
|
||||
"MUTED": "ミュートされています",
|
||||
"MAX_VOLUME": "最大音量",
|
||||
"RTC_MODE_OFF": "AEC 無効",
|
||||
"RTC_MODE_ON": "AEC 有効",
|
||||
"DOWNLOAD_ASSETS_FAILED": "アセットのダウンロードに失敗しました",
|
||||
"LOADING_ASSETS": "アセットを読み込み中...",
|
||||
"PLEASE_WAIT": "お待ちください...",
|
||||
"FOUND_NEW_ASSETS": "新しいアセットが見つかりました: %s",
|
||||
"HELLO_MY_FRIEND": "こんにちは、友達!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "ja-JP"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "警告",
|
||||
"INFO": "情報",
|
||||
"ERROR": "エラー",
|
||||
"VERSION": "バージョン ",
|
||||
"LOADING_PROTOCOL": "サーバーにログイン中...",
|
||||
"INITIALIZING": "初期化中...",
|
||||
"PIN_ERROR": "SIMカードを挿入してください",
|
||||
"REG_ERROR": "ネットワークに接続できません。ネットワーク状態を確認してください",
|
||||
"DETECTING_MODULE": "モジュールを検出中...",
|
||||
"REGISTERING_NETWORK": "ネットワーク接続待機中...",
|
||||
"CHECKING_NEW_VERSION": "新しいバージョンを確認中...",
|
||||
"CHECK_NEW_VERSION_FAILED": "更新確認に失敗しました。%d 秒後に再試行します: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fiに切り替え中...",
|
||||
"SWITCH_TO_4G_NETWORK": "4Gに切り替え中...",
|
||||
"STANDBY": "待機中",
|
||||
"CONNECT_TO": "接続先 ",
|
||||
"CONNECTING": "接続中...",
|
||||
"CONNECTED_TO": "接続完了 ",
|
||||
"LISTENING": "リスニング中...",
|
||||
"SPEAKING": "話しています...",
|
||||
"SERVER_NOT_FOUND": "利用可能なサーバーを探しています",
|
||||
"SERVER_NOT_CONNECTED": "サーバーに接続できません。後でもう一度お試しください",
|
||||
"SERVER_TIMEOUT": "応答待機時間が終了しました",
|
||||
"SERVER_ERROR": "送信に失敗しました。ネットワークを確認してください",
|
||||
"CONNECT_TO_HOTSPOT": "スマートフォンをWi-Fi ",
|
||||
"ACCESS_VIA_BROWSER": " に接続し、ブラウザでアクセスしてください ",
|
||||
"WIFI_CONFIG_MODE": "ネットワーク設定モード",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "ネットワーク設定中...",
|
||||
"SCANNING_WIFI": "Wi-Fiをスキャン中...",
|
||||
"NEW_VERSION": "新しいバージョン ",
|
||||
"OTA_UPGRADE": "OTAアップグレード",
|
||||
"UPGRADING": "システムをアップグレード中...",
|
||||
"UPGRADE_FAILED": "アップグレード失敗",
|
||||
"ACTIVATION": "デバイスをアクティベート",
|
||||
"BATTERY_LOW": "バッテリーが少なくなっています",
|
||||
"BATTERY_CHARGING": "充電中",
|
||||
"BATTERY_FULL": "バッテリー満タン",
|
||||
"BATTERY_NEED_CHARGE": "バッテリーが低下しています。充電してください",
|
||||
"VOLUME": "音量 ",
|
||||
"MUTED": "ミュートされています",
|
||||
"MAX_VOLUME": "最大音量",
|
||||
"RTC_MODE_OFF": "AEC 無効",
|
||||
"RTC_MODE_ON": "AEC 有効",
|
||||
"DOWNLOAD_ASSETS_FAILED": "アセットのダウンロードに失敗しました",
|
||||
"LOADING_ASSETS": "アセットを読み込み中...",
|
||||
"PLEASE_WAIT": "お待ちください...",
|
||||
"FOUND_NEW_ASSETS": "新しいアセットが見つかりました: %s",
|
||||
"HELLO_MY_FRIEND": "こんにちは、友達!"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,56 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "ko-KR"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "경고",
|
||||
"INFO": "정보",
|
||||
"ERROR": "오류",
|
||||
"VERSION": "버전 ",
|
||||
"LOADING_PROTOCOL": "로그인 중...",
|
||||
"INITIALIZING": "초기화 중...",
|
||||
"PIN_ERROR": "SIM 카드를 삽입하세요",
|
||||
"REG_ERROR": "네트워크에 접속할 수 없습니다. SIM 카드 상태를 확인하세요",
|
||||
"DETECTING_MODULE": "모듈 감지 중...",
|
||||
"REGISTERING_NETWORK": "네트워크 대기 중...",
|
||||
"CHECKING_NEW_VERSION": "새 버전 확인 중...",
|
||||
"CHECK_NEW_VERSION_FAILED": "새 버전 확인에 실패했습니다. %d초 후에 다시 시도합니다: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fi로 전환 중...",
|
||||
"SWITCH_TO_4G_NETWORK": "4G로 전환 중...",
|
||||
"STANDBY": "대기",
|
||||
"CONNECT_TO": "연결 대상: ",
|
||||
"CONNECTING": "연결 중...",
|
||||
"CONNECTION_SUCCESSFUL": "연결 성공",
|
||||
"CONNECTED_TO": "연결됨: ",
|
||||
"LISTENING": "듣는 중...",
|
||||
"SPEAKING": "말하는 중...",
|
||||
"SERVER_NOT_FOUND": "사용 가능한 서비스를 찾는 중",
|
||||
"SERVER_NOT_CONNECTED": "서비스에 연결할 수 없습니다. 나중에 다시 시도하세요",
|
||||
"SERVER_TIMEOUT": "응답 대기 시간 초과",
|
||||
"SERVER_ERROR": "전송 실패, 네트워크를 확인하세요",
|
||||
"CONNECT_TO_HOTSPOT": "핫스팟: ",
|
||||
"ACCESS_VIA_BROWSER": " 설정 URL: ",
|
||||
"WIFI_CONFIG_MODE": "Wi-Fi 설정 모드",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Wi-Fi 설정 모드 진입 중...",
|
||||
"SCANNING_WIFI": "Wi-Fi 스캔 중...",
|
||||
"NEW_VERSION": "새 버전 ",
|
||||
"OTA_UPGRADE": "OTA 업그레이드",
|
||||
"UPGRADING": "시스템 업그레이드 중...",
|
||||
"UPGRADE_FAILED": "업그레이드 실패",
|
||||
"ACTIVATION": "활성화",
|
||||
"BATTERY_LOW": "배터리 부족",
|
||||
"BATTERY_CHARGING": "충전 중",
|
||||
"BATTERY_FULL": "배터리 완충",
|
||||
"BATTERY_NEED_CHARGE": "배터리 부족, 충전하세요",
|
||||
"VOLUME": "볼륨 ",
|
||||
"MUTED": "음소거",
|
||||
"MAX_VOLUME": "최대 볼륨",
|
||||
"RTC_MODE_OFF": "AEC 끄기",
|
||||
"RTC_MODE_ON": "AEC 켜기",
|
||||
"DOWNLOAD_ASSETS_FAILED": "에셋 다운로드 실패",
|
||||
"LOADING_ASSETS": "에셋 로딩 중...",
|
||||
"PLEASE_WAIT": "잠시 기다려 주세요...",
|
||||
"FOUND_NEW_ASSETS": "새로운 에셋을 발견했습니다: %s",
|
||||
"HELLO_MY_FRIEND": "안녕하세요, 친구!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "ko-KR"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "경고",
|
||||
"INFO": "정보",
|
||||
"ERROR": "오류",
|
||||
"VERSION": "버전 ",
|
||||
"LOADING_PROTOCOL": "로그인 중...",
|
||||
"INITIALIZING": "초기화 중...",
|
||||
"PIN_ERROR": "SIM 카드를 삽입하세요",
|
||||
"REG_ERROR": "네트워크에 접속할 수 없습니다. SIM 카드 상태를 확인하세요",
|
||||
"DETECTING_MODULE": "모듈 감지 중...",
|
||||
"REGISTERING_NETWORK": "네트워크 대기 중...",
|
||||
"CHECKING_NEW_VERSION": "새 버전 확인 중...",
|
||||
"CHECK_NEW_VERSION_FAILED": "새 버전 확인에 실패했습니다. %d초 후에 다시 시도합니다: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fi로 전환 중...",
|
||||
"SWITCH_TO_4G_NETWORK": "4G로 전환 중...",
|
||||
"STANDBY": "대기",
|
||||
"CONNECT_TO": "연결 대상: ",
|
||||
"CONNECTING": "연결 중...",
|
||||
"CONNECTION_SUCCESSFUL": "연결 성공",
|
||||
"CONNECTED_TO": "연결됨: ",
|
||||
"LISTENING": "듣는 중...",
|
||||
"SPEAKING": "말하는 중...",
|
||||
"SERVER_NOT_FOUND": "사용 가능한 서비스를 찾는 중",
|
||||
"SERVER_NOT_CONNECTED": "서비스에 연결할 수 없습니다. 나중에 다시 시도하세요",
|
||||
"SERVER_TIMEOUT": "응답 대기 시간 초과",
|
||||
"SERVER_ERROR": "전송 실패, 네트워크를 확인하세요",
|
||||
"CONNECT_TO_HOTSPOT": "핫스팟: ",
|
||||
"ACCESS_VIA_BROWSER": " 설정 URL: ",
|
||||
"WIFI_CONFIG_MODE": "Wi-Fi 설정 모드",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Wi-Fi 설정 모드 진입 중...",
|
||||
"SCANNING_WIFI": "Wi-Fi 스캔 중...",
|
||||
"NEW_VERSION": "새 버전 ",
|
||||
"OTA_UPGRADE": "OTA 업그레이드",
|
||||
"UPGRADING": "시스템 업그레이드 중...",
|
||||
"UPGRADE_FAILED": "업그레이드 실패",
|
||||
"ACTIVATION": "활성화",
|
||||
"BATTERY_LOW": "배터리 부족",
|
||||
"BATTERY_CHARGING": "충전 중",
|
||||
"BATTERY_FULL": "배터리 완충",
|
||||
"BATTERY_NEED_CHARGE": "배터리 부족, 충전하세요",
|
||||
"VOLUME": "볼륨 ",
|
||||
"MUTED": "음소거",
|
||||
"MAX_VOLUME": "최대 볼륨",
|
||||
"RTC_MODE_OFF": "AEC 끄기",
|
||||
"RTC_MODE_ON": "AEC 켜기",
|
||||
"DOWNLOAD_ASSETS_FAILED": "에셋 다운로드 실패",
|
||||
"LOADING_ASSETS": "에셋 로딩 중...",
|
||||
"PLEASE_WAIT": "잠시 기다려 주세요...",
|
||||
"FOUND_NEW_ASSETS": "새로운 에셋을 발견했습니다: %s",
|
||||
"HELLO_MY_FRIEND": "안녕하세요, 친구!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "pl-PL"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Ostrzeżenie",
|
||||
"INFO": "Informacja",
|
||||
"ERROR": "Błąd",
|
||||
"VERSION": "Wersja ",
|
||||
"LOADING_PROTOCOL": "Łączenie z serwerem...",
|
||||
"INITIALIZING": "Inicjalizacja...",
|
||||
"PIN_ERROR": "Proszę włożyć kartę SIM",
|
||||
"REG_ERROR": "Nie można uzyskać dostępu do sieci, sprawdź stan karty danych",
|
||||
"DETECTING_MODULE": "Wykrywanie modułu...",
|
||||
"REGISTERING_NETWORK": "Oczekiwanie na sieć...",
|
||||
"CHECKING_NEW_VERSION": "Sprawdzanie nowej wersji...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Sprawdzanie nowej wersji nie powiodło się, ponowna próba za %d sekund: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Przełączanie na Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Przełączanie na 4G...",
|
||||
"STANDBY": "Gotowość",
|
||||
"CONNECT_TO": "Połącz z ",
|
||||
"CONNECTING": "Łączenie...",
|
||||
"CONNECTED_TO": "Połączono z ",
|
||||
"LISTENING": "Słuchanie...",
|
||||
"SPEAKING": "Mówienie...",
|
||||
"SERVER_NOT_FOUND": "Szukanie dostępnej usługi",
|
||||
"SERVER_NOT_CONNECTED": "Nie można połączyć się z usługą, spróbuj ponownie później",
|
||||
"SERVER_TIMEOUT": "Przekroczono czas oczekiwania na odpowiedź",
|
||||
"SERVER_ERROR": "Wysyłanie nie powiodło się, sprawdź sieć",
|
||||
"CONNECT_TO_HOTSPOT": "Podłącz telefon do hotspotu ",
|
||||
"ACCESS_VIA_BROWSER": ",dostęp przez przeglądarkę ",
|
||||
"WIFI_CONFIG_MODE": "Tryb konfiguracji sieci",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Wchodzenie w tryb konfiguracji sieci...",
|
||||
"SCANNING_WIFI": "Skanowanie Wi-Fi...",
|
||||
"NEW_VERSION": "Nowa wersja ",
|
||||
"OTA_UPGRADE": "Aktualizacja OTA",
|
||||
"UPGRADING": "Aktualizacja systemu...",
|
||||
"UPGRADE_FAILED": "Aktualizacja nie powiodła się",
|
||||
"ACTIVATION": "Aktywacja urządzenia",
|
||||
"BATTERY_LOW": "Niski poziom baterii",
|
||||
"BATTERY_CHARGING": "Ładowanie",
|
||||
"BATTERY_FULL": "Bateria pełna",
|
||||
"BATTERY_NEED_CHARGE": "Niski poziom baterii, proszę naładować",
|
||||
"VOLUME": "Głośność ",
|
||||
"MUTED": "Wyciszony",
|
||||
"MAX_VOLUME": "Maksymalna głośność",
|
||||
"RTC_MODE_OFF": "AEC wyłączony",
|
||||
"RTC_MODE_ON": "AEC włączony",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Nie udało się pobrać zasobów",
|
||||
"LOADING_ASSETS": "Ładowanie zasobów...",
|
||||
"PLEASE_WAIT": "Proszę czekać...",
|
||||
"FOUND_NEW_ASSETS": "Znaleziono nowe zasoby: %s",
|
||||
"HELLO_MY_FRIEND": "Cześć, mój przyjacielu!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "pl-PL"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Ostrzeżenie",
|
||||
"INFO": "Informacja",
|
||||
"ERROR": "Błąd",
|
||||
"VERSION": "Wersja ",
|
||||
"LOADING_PROTOCOL": "Łączenie z serwerem...",
|
||||
"INITIALIZING": "Inicjalizacja...",
|
||||
"PIN_ERROR": "Proszę włożyć kartę SIM",
|
||||
"REG_ERROR": "Nie można uzyskać dostępu do sieci, sprawdź stan karty danych",
|
||||
"DETECTING_MODULE": "Wykrywanie modułu...",
|
||||
"REGISTERING_NETWORK": "Oczekiwanie na sieć...",
|
||||
"CHECKING_NEW_VERSION": "Sprawdzanie nowej wersji...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Sprawdzanie nowej wersji nie powiodło się, ponowna próba za %d sekund: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Przełączanie na Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Przełączanie na 4G...",
|
||||
"STANDBY": "Gotowość",
|
||||
"CONNECT_TO": "Połącz z ",
|
||||
"CONNECTING": "Łączenie...",
|
||||
"CONNECTED_TO": "Połączono z ",
|
||||
"LISTENING": "Słuchanie...",
|
||||
"SPEAKING": "Mówienie...",
|
||||
"SERVER_NOT_FOUND": "Szukanie dostępnej usługi",
|
||||
"SERVER_NOT_CONNECTED": "Nie można połączyć się z usługą, spróbuj ponownie później",
|
||||
"SERVER_TIMEOUT": "Przekroczono czas oczekiwania na odpowiedź",
|
||||
"SERVER_ERROR": "Wysyłanie nie powiodło się, sprawdź sieć",
|
||||
"CONNECT_TO_HOTSPOT": "Podłącz telefon do hotspotu ",
|
||||
"ACCESS_VIA_BROWSER": ",dostęp przez przeglądarkę ",
|
||||
"WIFI_CONFIG_MODE": "Tryb konfiguracji sieci",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Wchodzenie w tryb konfiguracji sieci...",
|
||||
"SCANNING_WIFI": "Skanowanie Wi-Fi...",
|
||||
"NEW_VERSION": "Nowa wersja ",
|
||||
"OTA_UPGRADE": "Aktualizacja OTA",
|
||||
"UPGRADING": "Aktualizacja systemu...",
|
||||
"UPGRADE_FAILED": "Aktualizacja nie powiodła się",
|
||||
"ACTIVATION": "Aktywacja urządzenia",
|
||||
"BATTERY_LOW": "Niski poziom baterii",
|
||||
"BATTERY_CHARGING": "Ładowanie",
|
||||
"BATTERY_FULL": "Bateria pełna",
|
||||
"BATTERY_NEED_CHARGE": "Niski poziom baterii, proszę naładować",
|
||||
"VOLUME": "Głośność ",
|
||||
"MUTED": "Wyciszony",
|
||||
"MAX_VOLUME": "Maksymalna głośność",
|
||||
"RTC_MODE_OFF": "AEC wyłączony",
|
||||
"RTC_MODE_ON": "AEC włączony",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Nie udało się pobrać zasobów",
|
||||
"LOADING_ASSETS": "Ładowanie zasobów...",
|
||||
"PLEASE_WAIT": "Proszę czekać...",
|
||||
"FOUND_NEW_ASSETS": "Znaleziono nowe zasoby: %s",
|
||||
"HELLO_MY_FRIEND": "Cześć, mój przyjacielu!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "pt-PT"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Aviso",
|
||||
"INFO": "Informação",
|
||||
"ERROR": "Erro",
|
||||
"VERSION": "Versão ",
|
||||
"LOADING_PROTOCOL": "Ligando ao servidor...",
|
||||
"INITIALIZING": "A inicializar...",
|
||||
"PIN_ERROR": "Por favor insira o cartão SIM",
|
||||
"REG_ERROR": "Não é possível aceder à rede, verifique o estado do cartão de dados",
|
||||
"DETECTING_MODULE": "A detectar módulo...",
|
||||
"REGISTERING_NETWORK": "À espera da rede...",
|
||||
"CHECKING_NEW_VERSION": "A verificar nova versão...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Falha na verificação de nova versão, nova tentativa em %d segundos: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "A mudar para Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "A mudar para 4G...",
|
||||
"STANDBY": "Em espera",
|
||||
"CONNECT_TO": "Ligar a ",
|
||||
"CONNECTING": "A ligar...",
|
||||
"CONNECTED_TO": "Ligado a ",
|
||||
"LISTENING": "A escutar...",
|
||||
"SPEAKING": "A falar...",
|
||||
"SERVER_NOT_FOUND": "A procurar serviço disponível",
|
||||
"SERVER_NOT_CONNECTED": "Não é possível ligar ao serviço, tente mais tarde",
|
||||
"SERVER_TIMEOUT": "Tempo limite de resposta",
|
||||
"SERVER_ERROR": "Falha no envio, verifique a rede",
|
||||
"CONNECT_TO_HOTSPOT": "Ligue o telefone ao hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",aceder através do navegador ",
|
||||
"WIFI_CONFIG_MODE": "Modo de configuração de rede",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "A entrar no modo de configuração de rede...",
|
||||
"SCANNING_WIFI": "A procurar Wi-Fi...",
|
||||
"NEW_VERSION": "Nova versão ",
|
||||
"OTA_UPGRADE": "Atualização OTA",
|
||||
"UPGRADING": "A atualizar sistema...",
|
||||
"UPGRADE_FAILED": "Atualização falhada",
|
||||
"ACTIVATION": "Ativação do dispositivo",
|
||||
"BATTERY_LOW": "Bateria fraca",
|
||||
"BATTERY_CHARGING": "A carregar",
|
||||
"BATTERY_FULL": "Bateria cheia",
|
||||
"BATTERY_NEED_CHARGE": "Bateria fraca, por favor carregue",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Silenciado",
|
||||
"MAX_VOLUME": "Volume máximo",
|
||||
"RTC_MODE_OFF": "AEC desligado",
|
||||
"RTC_MODE_ON": "AEC ligado",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Falha ao descarregar recursos",
|
||||
"LOADING_ASSETS": "A carregar recursos...",
|
||||
"PLEASE_WAIT": "Por favor aguarde...",
|
||||
"FOUND_NEW_ASSETS": "Encontrados novos recursos: %s",
|
||||
"HELLO_MY_FRIEND": "Olá, meu amigo!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "pt-PT"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Aviso",
|
||||
"INFO": "Informação",
|
||||
"ERROR": "Erro",
|
||||
"VERSION": "Versão ",
|
||||
"LOADING_PROTOCOL": "Ligando ao servidor...",
|
||||
"INITIALIZING": "A inicializar...",
|
||||
"PIN_ERROR": "Por favor insira o cartão SIM",
|
||||
"REG_ERROR": "Não é possível aceder à rede, verifique o estado do cartão de dados",
|
||||
"DETECTING_MODULE": "A detectar módulo...",
|
||||
"REGISTERING_NETWORK": "À espera da rede...",
|
||||
"CHECKING_NEW_VERSION": "A verificar nova versão...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Falha na verificação de nova versão, nova tentativa em %d segundos: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "A mudar para Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "A mudar para 4G...",
|
||||
"STANDBY": "Em espera",
|
||||
"CONNECT_TO": "Ligar a ",
|
||||
"CONNECTING": "A ligar...",
|
||||
"CONNECTED_TO": "Ligado a ",
|
||||
"LISTENING": "A escutar...",
|
||||
"SPEAKING": "A falar...",
|
||||
"SERVER_NOT_FOUND": "A procurar serviço disponível",
|
||||
"SERVER_NOT_CONNECTED": "Não é possível ligar ao serviço, tente mais tarde",
|
||||
"SERVER_TIMEOUT": "Tempo limite de resposta",
|
||||
"SERVER_ERROR": "Falha no envio, verifique a rede",
|
||||
"CONNECT_TO_HOTSPOT": "Ligue o telefone ao hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",aceder através do navegador ",
|
||||
"WIFI_CONFIG_MODE": "Modo de configuração de rede",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "A entrar no modo de configuração de rede...",
|
||||
"SCANNING_WIFI": "A procurar Wi-Fi...",
|
||||
"NEW_VERSION": "Nova versão ",
|
||||
"OTA_UPGRADE": "Atualização OTA",
|
||||
"UPGRADING": "A atualizar sistema...",
|
||||
"UPGRADE_FAILED": "Atualização falhada",
|
||||
"ACTIVATION": "Ativação do dispositivo",
|
||||
"BATTERY_LOW": "Bateria fraca",
|
||||
"BATTERY_CHARGING": "A carregar",
|
||||
"BATTERY_FULL": "Bateria cheia",
|
||||
"BATTERY_NEED_CHARGE": "Bateria fraca, por favor carregue",
|
||||
"VOLUME": "Volume ",
|
||||
"MUTED": "Silenciado",
|
||||
"MAX_VOLUME": "Volume máximo",
|
||||
"RTC_MODE_OFF": "AEC desligado",
|
||||
"RTC_MODE_ON": "AEC ligado",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Falha ao descarregar recursos",
|
||||
"LOADING_ASSETS": "A carregar recursos...",
|
||||
"PLEASE_WAIT": "Por favor aguarde...",
|
||||
"FOUND_NEW_ASSETS": "Encontrados novos recursos: %s",
|
||||
"HELLO_MY_FRIEND": "Olá, meu amigo!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "ro-RO"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Avertisment",
|
||||
"INFO": "Informație",
|
||||
"ERROR": "Eroare",
|
||||
"VERSION": "Versiune ",
|
||||
"LOADING_PROTOCOL": "Se conectează la server...",
|
||||
"INITIALIZING": "Se inițializează...",
|
||||
"PIN_ERROR": "Vă rugăm să introduceți cardul SIM",
|
||||
"REG_ERROR": "Nu se poate accesa rețeaua, verificați starea cardului de date",
|
||||
"DETECTING_MODULE": "Se detectează modulul...",
|
||||
"REGISTERING_NETWORK": "Se așteaptă rețeaua...",
|
||||
"CHECKING_NEW_VERSION": "Se verifică versiunea nouă...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Verificarea versiunii noi a eșuat, se reîncearcă în %d secunde: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Se comută la Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Se comută la 4G...",
|
||||
"STANDBY": "În așteptare",
|
||||
"CONNECT_TO": "Conectare la ",
|
||||
"CONNECTING": "Se conectează...",
|
||||
"CONNECTED_TO": "Conectat la ",
|
||||
"LISTENING": "Se ascultă...",
|
||||
"SPEAKING": "Se vorbește...",
|
||||
"SERVER_NOT_FOUND": "Se caută serviciul disponibil",
|
||||
"SERVER_NOT_CONNECTED": "Nu se poate conecta la serviciu, încercați mai târziu",
|
||||
"SERVER_TIMEOUT": "Timpul de răspuns a expirat",
|
||||
"SERVER_ERROR": "Trimiterea a eșuat, verificați rețeaua",
|
||||
"CONNECT_TO_HOTSPOT": "Conectați telefonul la hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",accesați prin browser ",
|
||||
"WIFI_CONFIG_MODE": "Modul de configurare rețea",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Se intră în modul de configurare rețea...",
|
||||
"SCANNING_WIFI": "Se scanează Wi-Fi...",
|
||||
"NEW_VERSION": "Versiune nouă ",
|
||||
"OTA_UPGRADE": "Actualizare OTA",
|
||||
"UPGRADING": "Se actualizează sistemul...",
|
||||
"UPGRADE_FAILED": "Actualizarea a eșuat",
|
||||
"ACTIVATION": "Activarea dispozitivului",
|
||||
"BATTERY_LOW": "Baterie scăzută",
|
||||
"BATTERY_CHARGING": "Se încarcă",
|
||||
"BATTERY_FULL": "Baterie plină",
|
||||
"BATTERY_NEED_CHARGE": "Baterie scăzută, vă rugăm să încărcați",
|
||||
"VOLUME": "Volum ",
|
||||
"MUTED": "Silențios",
|
||||
"MAX_VOLUME": "Volum maxim",
|
||||
"RTC_MODE_OFF": "AEC oprit",
|
||||
"RTC_MODE_ON": "AEC pornit",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Eșec la descărcarea resurselor",
|
||||
"LOADING_ASSETS": "Se încarcă resursele...",
|
||||
"PLEASE_WAIT": "Vă rugăm să așteptați...",
|
||||
"FOUND_NEW_ASSETS": "S-au găsit resurse noi: %s",
|
||||
"HELLO_MY_FRIEND": "Salut, prietenul meu!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "ro-RO"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Avertisment",
|
||||
"INFO": "Informație",
|
||||
"ERROR": "Eroare",
|
||||
"VERSION": "Versiune ",
|
||||
"LOADING_PROTOCOL": "Se conectează la server...",
|
||||
"INITIALIZING": "Se inițializează...",
|
||||
"PIN_ERROR": "Vă rugăm să introduceți cardul SIM",
|
||||
"REG_ERROR": "Nu se poate accesa rețeaua, verificați starea cardului de date",
|
||||
"DETECTING_MODULE": "Se detectează modulul...",
|
||||
"REGISTERING_NETWORK": "Se așteaptă rețeaua...",
|
||||
"CHECKING_NEW_VERSION": "Se verifică versiunea nouă...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Verificarea versiunii noi a eșuat, se reîncearcă în %d secunde: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Se comută la Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Se comută la 4G...",
|
||||
"STANDBY": "În așteptare",
|
||||
"CONNECT_TO": "Conectare la ",
|
||||
"CONNECTING": "Se conectează...",
|
||||
"CONNECTED_TO": "Conectat la ",
|
||||
"LISTENING": "Se ascultă...",
|
||||
"SPEAKING": "Se vorbește...",
|
||||
"SERVER_NOT_FOUND": "Se caută serviciul disponibil",
|
||||
"SERVER_NOT_CONNECTED": "Nu se poate conecta la serviciu, încercați mai târziu",
|
||||
"SERVER_TIMEOUT": "Timpul de răspuns a expirat",
|
||||
"SERVER_ERROR": "Trimiterea a eșuat, verificați rețeaua",
|
||||
"CONNECT_TO_HOTSPOT": "Conectați telefonul la hotspot ",
|
||||
"ACCESS_VIA_BROWSER": ",accesați prin browser ",
|
||||
"WIFI_CONFIG_MODE": "Modul de configurare rețea",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Se intră în modul de configurare rețea...",
|
||||
"SCANNING_WIFI": "Se scanează Wi-Fi...",
|
||||
"NEW_VERSION": "Versiune nouă ",
|
||||
"OTA_UPGRADE": "Actualizare OTA",
|
||||
"UPGRADING": "Se actualizează sistemul...",
|
||||
"UPGRADE_FAILED": "Actualizarea a eșuat",
|
||||
"ACTIVATION": "Activarea dispozitivului",
|
||||
"BATTERY_LOW": "Baterie scăzută",
|
||||
"BATTERY_CHARGING": "Se încarcă",
|
||||
"BATTERY_FULL": "Baterie plină",
|
||||
"BATTERY_NEED_CHARGE": "Baterie scăzută, vă rugăm să încărcați",
|
||||
"VOLUME": "Volum ",
|
||||
"MUTED": "Silențios",
|
||||
"MAX_VOLUME": "Volum maxim",
|
||||
"RTC_MODE_OFF": "AEC oprit",
|
||||
"RTC_MODE_ON": "AEC pornit",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Eșec la descărcarea resurselor",
|
||||
"LOADING_ASSETS": "Se încarcă resursele...",
|
||||
"PLEASE_WAIT": "Vă rugăm să așteptați...",
|
||||
"FOUND_NEW_ASSETS": "S-au găsit resurse noi: %s",
|
||||
"HELLO_MY_FRIEND": "Salut, prietenul meu!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "ru-RU"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Предупреждение",
|
||||
"INFO": "Информация",
|
||||
"ERROR": "Ошибка",
|
||||
"VERSION": "Версия ",
|
||||
"LOADING_PROTOCOL": "Подключение к серверу...",
|
||||
"INITIALIZING": "Инициализация...",
|
||||
"PIN_ERROR": "Пожалуйста, вставьте SIM-карту",
|
||||
"REG_ERROR": "Невозможно подключиться к сети, проверьте состояние карты данных",
|
||||
"DETECTING_MODULE": "Обнаружение модуля...",
|
||||
"REGISTERING_NETWORK": "Ожидание сети...",
|
||||
"CHECKING_NEW_VERSION": "Проверка новой версии...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Ошибка проверки новой версии, повтор через %d секунд: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Переключение на Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Переключение на 4G...",
|
||||
"STANDBY": "Ожидание",
|
||||
"CONNECT_TO": "Подключение к ",
|
||||
"CONNECTING": "Подключение...",
|
||||
"CONNECTED_TO": "Подключено к ",
|
||||
"LISTENING": "Прослушивание...",
|
||||
"SPEAKING": "Говорение...",
|
||||
"SERVER_NOT_FOUND": "Поиск доступного сервиса",
|
||||
"SERVER_NOT_CONNECTED": "Невозможно подключиться к сервису, попробуйте позже",
|
||||
"SERVER_TIMEOUT": "Тайм-аут ответа",
|
||||
"SERVER_ERROR": "Ошибка отправки, проверьте сеть",
|
||||
"CONNECT_TO_HOTSPOT": "Подключите телефон к точке доступа ",
|
||||
"ACCESS_VIA_BROWSER": ",доступ через браузер ",
|
||||
"WIFI_CONFIG_MODE": "Режим настройки сети",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Вход в режим настройки сети...",
|
||||
"SCANNING_WIFI": "Сканирование Wi-Fi...",
|
||||
"NEW_VERSION": "Новая версия ",
|
||||
"OTA_UPGRADE": "Обновление OTA",
|
||||
"UPGRADING": "Обновление системы...",
|
||||
"UPGRADE_FAILED": "Обновление не удалось",
|
||||
"ACTIVATION": "Активация устройства",
|
||||
"BATTERY_LOW": "Низкий заряд батареи",
|
||||
"BATTERY_CHARGING": "Зарядка",
|
||||
"BATTERY_FULL": "Батарея полная",
|
||||
"BATTERY_NEED_CHARGE": "Низкий заряд, пожалуйста, зарядите",
|
||||
"VOLUME": "Громкость ",
|
||||
"MUTED": "Звук отключен",
|
||||
"MAX_VOLUME": "Максимальная громкость",
|
||||
"RTC_MODE_OFF": "AEC выключен",
|
||||
"RTC_MODE_ON": "AEC включен",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Не удалось загрузить ресурсы",
|
||||
"LOADING_ASSETS": "Загрузка ресурсов...",
|
||||
"PLEASE_WAIT": "Пожалуйста, подождите...",
|
||||
"FOUND_NEW_ASSETS": "Найдены новые ресурсы: %s",
|
||||
"HELLO_MY_FRIEND": "Привет, мой друг!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "ru-RU"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Предупреждение",
|
||||
"INFO": "Информация",
|
||||
"ERROR": "Ошибка",
|
||||
"VERSION": "Версия ",
|
||||
"LOADING_PROTOCOL": "Подключение к серверу...",
|
||||
"INITIALIZING": "Инициализация...",
|
||||
"PIN_ERROR": "Пожалуйста, вставьте SIM-карту",
|
||||
"REG_ERROR": "Невозможно подключиться к сети, проверьте состояние карты данных",
|
||||
"DETECTING_MODULE": "Обнаружение модуля...",
|
||||
"REGISTERING_NETWORK": "Ожидание сети...",
|
||||
"CHECKING_NEW_VERSION": "Проверка новой версии...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Ошибка проверки новой версии, повтор через %d секунд: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Переключение на Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Переключение на 4G...",
|
||||
"STANDBY": "Ожидание",
|
||||
"CONNECT_TO": "Подключение к ",
|
||||
"CONNECTING": "Подключение...",
|
||||
"CONNECTED_TO": "Подключено к ",
|
||||
"LISTENING": "Прослушивание...",
|
||||
"SPEAKING": "Говорение...",
|
||||
"SERVER_NOT_FOUND": "Поиск доступного сервиса",
|
||||
"SERVER_NOT_CONNECTED": "Невозможно подключиться к сервису, попробуйте позже",
|
||||
"SERVER_TIMEOUT": "Тайм-аут ответа",
|
||||
"SERVER_ERROR": "Ошибка отправки, проверьте сеть",
|
||||
"CONNECT_TO_HOTSPOT": "Подключите телефон к точке доступа ",
|
||||
"ACCESS_VIA_BROWSER": ",доступ через браузер ",
|
||||
"WIFI_CONFIG_MODE": "Режим настройки сети",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Вход в режим настройки сети...",
|
||||
"SCANNING_WIFI": "Сканирование Wi-Fi...",
|
||||
"NEW_VERSION": "Новая версия ",
|
||||
"OTA_UPGRADE": "Обновление OTA",
|
||||
"UPGRADING": "Обновление системы...",
|
||||
"UPGRADE_FAILED": "Обновление не удалось",
|
||||
"ACTIVATION": "Активация устройства",
|
||||
"BATTERY_LOW": "Низкий заряд батареи",
|
||||
"BATTERY_CHARGING": "Зарядка",
|
||||
"BATTERY_FULL": "Батарея полная",
|
||||
"BATTERY_NEED_CHARGE": "Низкий заряд, пожалуйста, зарядите",
|
||||
"VOLUME": "Громкость ",
|
||||
"MUTED": "Звук отключен",
|
||||
"MAX_VOLUME": "Максимальная громкость",
|
||||
"RTC_MODE_OFF": "AEC выключен",
|
||||
"RTC_MODE_ON": "AEC включен",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Не удалось загрузить ресурсы",
|
||||
"LOADING_ASSETS": "Загрузка ресурсов...",
|
||||
"PLEASE_WAIT": "Пожалуйста, подождите...",
|
||||
"FOUND_NEW_ASSETS": "Найдены новые ресурсы: %s",
|
||||
"HELLO_MY_FRIEND": "Привет, мой друг!"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,56 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "th-TH"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "คำเตือน",
|
||||
"INFO": "ข้อมูล",
|
||||
"ERROR": "ข้อผิดพลาด",
|
||||
"VERSION": "เวอร์ชัน ",
|
||||
"LOADING_PROTOCOL": "กำลังเข้าสู่ระบบ...",
|
||||
"INITIALIZING": "กำลังเริ่มต้นระบบ...",
|
||||
"PIN_ERROR": "กรุณาใส่ซิมการ์ด",
|
||||
"REG_ERROR": "ไม่สามารถเข้าถึงเครือข่ายได้ กรุณาตรวจสอบสถานะซิมการ์ด",
|
||||
"DETECTING_MODULE": "กำลังตรวจจับโมดูล...",
|
||||
"REGISTERING_NETWORK": "กำลังรอเครือข่าย...",
|
||||
"CHECKING_NEW_VERSION": "กำลังตรวจสอบเวอร์ชันใหม่...",
|
||||
"CHECK_NEW_VERSION_FAILED": "การตรวจสอบเวอร์ชันใหม่ล้มเหลว จะลองใหม่ใน %d วินาที: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "กำลังเปลี่ยนเป็น Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "กำลังเปลี่ยนเป็น 4G...",
|
||||
"STANDBY": "พร้อม",
|
||||
"CONNECT_TO": "เชื่อมต่อกับ ",
|
||||
"CONNECTING": "กำลังเชื่อมต่อ...",
|
||||
"CONNECTION_SUCCESSFUL": "เชื่อมต่อสำเร็จ",
|
||||
"CONNECTED_TO": "เชื่อมต่อกับ ",
|
||||
"LISTENING": "กำลังฟัง...",
|
||||
"SPEAKING": "กำลังพูด...",
|
||||
"SERVER_NOT_FOUND": "กำลังค้นหาบริการที่ใช้งานได้",
|
||||
"SERVER_NOT_CONNECTED": "ไม่สามารถเชื่อมต่อกับบริการได้ กรุณาลองใหม่ในภายหลัง",
|
||||
"SERVER_TIMEOUT": "หมดเวลารอการตอบกลับ",
|
||||
"SERVER_ERROR": "การส่งข้อมูลล้มเหลว กรุณาตรวจสอบเครือข่าย",
|
||||
"CONNECT_TO_HOTSPOT": "ฮอตสปอต: ",
|
||||
"ACCESS_VIA_BROWSER": " URL การตั้งค่า: ",
|
||||
"WIFI_CONFIG_MODE": "โหมดการตั้งค่า Wi-Fi",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "กำลังเข้าสู่โหมดการตั้งค่า Wi-Fi...",
|
||||
"SCANNING_WIFI": "กำลังสแกน Wi-Fi...",
|
||||
"NEW_VERSION": "เวอร์ชันใหม่ ",
|
||||
"OTA_UPGRADE": "การอัปเกรด OTA",
|
||||
"UPGRADING": "ระบบกำลังอัปเกรด...",
|
||||
"UPGRADE_FAILED": "การอัปเกรดล้มเหลว",
|
||||
"ACTIVATION": "การเปิดใช้งาน",
|
||||
"BATTERY_LOW": "แบตเตอรี่ต่ำ",
|
||||
"BATTERY_CHARGING": "กำลังชาร์จ",
|
||||
"BATTERY_FULL": "แบตเตอรี่เต็ม",
|
||||
"BATTERY_NEED_CHARGE": "แบตเตอรี่ต่ำ กรุณาชาร์จ",
|
||||
"VOLUME": "เสียง ",
|
||||
"MUTED": "ปิดเสียง",
|
||||
"MAX_VOLUME": "เสียงสูงสุด",
|
||||
"RTC_MODE_OFF": "ปิด AEC",
|
||||
"RTC_MODE_ON": "เปิด AEC",
|
||||
"DOWNLOAD_ASSETS_FAILED": "ดาวน์โหลดทรัพยากรล้มเหลว",
|
||||
"LOADING_ASSETS": "กำลังโหลดทรัพยากร...",
|
||||
"PLEASE_WAIT": "กรุณารอสักครู่...",
|
||||
"FOUND_NEW_ASSETS": "พบทรัพยากรใหม่: %s",
|
||||
"HELLO_MY_FRIEND": "สวัสดี เพื่อนของฉัน!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "th-TH"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "คำเตือน",
|
||||
"INFO": "ข้อมูล",
|
||||
"ERROR": "ข้อผิดพลาด",
|
||||
"VERSION": "เวอร์ชัน ",
|
||||
"LOADING_PROTOCOL": "กำลังเข้าสู่ระบบ...",
|
||||
"INITIALIZING": "กำลังเริ่มต้นระบบ...",
|
||||
"PIN_ERROR": "กรุณาใส่ซิมการ์ด",
|
||||
"REG_ERROR": "ไม่สามารถเข้าถึงเครือข่ายได้ กรุณาตรวจสอบสถานะซิมการ์ด",
|
||||
"DETECTING_MODULE": "กำลังตรวจจับโมดูล...",
|
||||
"REGISTERING_NETWORK": "กำลังรอเครือข่าย...",
|
||||
"CHECKING_NEW_VERSION": "กำลังตรวจสอบเวอร์ชันใหม่...",
|
||||
"CHECK_NEW_VERSION_FAILED": "การตรวจสอบเวอร์ชันใหม่ล้มเหลว จะลองใหม่ใน %d วินาที: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "กำลังเปลี่ยนเป็น Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "กำลังเปลี่ยนเป็น 4G...",
|
||||
"STANDBY": "พร้อม",
|
||||
"CONNECT_TO": "เชื่อมต่อกับ ",
|
||||
"CONNECTING": "กำลังเชื่อมต่อ...",
|
||||
"CONNECTION_SUCCESSFUL": "เชื่อมต่อสำเร็จ",
|
||||
"CONNECTED_TO": "เชื่อมต่อกับ ",
|
||||
"LISTENING": "กำลังฟัง...",
|
||||
"SPEAKING": "กำลังพูด...",
|
||||
"SERVER_NOT_FOUND": "กำลังค้นหาบริการที่ใช้งานได้",
|
||||
"SERVER_NOT_CONNECTED": "ไม่สามารถเชื่อมต่อกับบริการได้ กรุณาลองใหม่ในภายหลัง",
|
||||
"SERVER_TIMEOUT": "หมดเวลารอการตอบกลับ",
|
||||
"SERVER_ERROR": "การส่งข้อมูลล้มเหลว กรุณาตรวจสอบเครือข่าย",
|
||||
"CONNECT_TO_HOTSPOT": "ฮอตสปอต: ",
|
||||
"ACCESS_VIA_BROWSER": " URL การตั้งค่า: ",
|
||||
"WIFI_CONFIG_MODE": "โหมดการตั้งค่า Wi-Fi",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "กำลังเข้าสู่โหมดการตั้งค่า Wi-Fi...",
|
||||
"SCANNING_WIFI": "กำลังสแกน Wi-Fi...",
|
||||
"NEW_VERSION": "เวอร์ชันใหม่ ",
|
||||
"OTA_UPGRADE": "การอัปเกรด OTA",
|
||||
"UPGRADING": "ระบบกำลังอัปเกรด...",
|
||||
"UPGRADE_FAILED": "การอัปเกรดล้มเหลว",
|
||||
"ACTIVATION": "การเปิดใช้งาน",
|
||||
"BATTERY_LOW": "แบตเตอรี่ต่ำ",
|
||||
"BATTERY_CHARGING": "กำลังชาร์จ",
|
||||
"BATTERY_FULL": "แบตเตอรี่เต็ม",
|
||||
"BATTERY_NEED_CHARGE": "แบตเตอรี่ต่ำ กรุณาชาร์จ",
|
||||
"VOLUME": "เสียง ",
|
||||
"MUTED": "ปิดเสียง",
|
||||
"MAX_VOLUME": "เสียงสูงสุด",
|
||||
"RTC_MODE_OFF": "ปิด AEC",
|
||||
"RTC_MODE_ON": "เปิด AEC",
|
||||
"DOWNLOAD_ASSETS_FAILED": "ดาวน์โหลดทรัพยากรล้มเหลว",
|
||||
"LOADING_ASSETS": "กำลังโหลดทรัพยากร...",
|
||||
"PLEASE_WAIT": "กรุณารอสักครู่...",
|
||||
"FOUND_NEW_ASSETS": "พบทรัพยากรใหม่: %s",
|
||||
"HELLO_MY_FRIEND": "สวัสดี เพื่อนของฉัน!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "tr-TR"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Uyarı",
|
||||
"INFO": "Bilgi",
|
||||
"ERROR": "Hata",
|
||||
"VERSION": "Sürüm ",
|
||||
"LOADING_PROTOCOL": "Sunucuya bağlanıyor...",
|
||||
"INITIALIZING": "Başlatılıyor...",
|
||||
"PIN_ERROR": "Lütfen SIM kartı takın",
|
||||
"REG_ERROR": "Ağa erişilemiyor, veri kartı durumunu kontrol edin",
|
||||
"DETECTING_MODULE": "Modül algılanıyor...",
|
||||
"REGISTERING_NETWORK": "Ağ bekleniyor...",
|
||||
"CHECKING_NEW_VERSION": "Yeni sürüm kontrol ediliyor...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Yeni sürüm kontrolü başarısız, %d saniye sonra tekrar denenecek: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fi'ye geçiliyor...",
|
||||
"SWITCH_TO_4G_NETWORK": "4G'ye geçiliyor...",
|
||||
"STANDBY": "Bekleme",
|
||||
"CONNECT_TO": "Bağlan ",
|
||||
"CONNECTING": "Bağlanıyor...",
|
||||
"CONNECTED_TO": "Bağlandı ",
|
||||
"LISTENING": "Dinleniyor...",
|
||||
"SPEAKING": "Konuşuluyor...",
|
||||
"SERVER_NOT_FOUND": "Mevcut hizmet aranıyor",
|
||||
"SERVER_NOT_CONNECTED": "Hizmete bağlanılamıyor, lütfen daha sonra deneyin",
|
||||
"SERVER_TIMEOUT": "Yanıt zaman aşımı",
|
||||
"SERVER_ERROR": "Gönderme başarısız, ağı kontrol edin",
|
||||
"CONNECT_TO_HOTSPOT": "Telefonu hotspot'a bağlayın ",
|
||||
"ACCESS_VIA_BROWSER": ",tarayıcı üzerinden erişin ",
|
||||
"WIFI_CONFIG_MODE": "Ağ yapılandırma modu",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Ağ yapılandırma moduna giriliyor...",
|
||||
"SCANNING_WIFI": "Wi-Fi taranıyor...",
|
||||
"NEW_VERSION": "Yeni sürüm ",
|
||||
"OTA_UPGRADE": "OTA güncelleme",
|
||||
"UPGRADING": "Sistem güncelleniyor...",
|
||||
"UPGRADE_FAILED": "Güncelleme başarısız",
|
||||
"ACTIVATION": "Cihaz aktivasyonu",
|
||||
"BATTERY_LOW": "Pil düşük",
|
||||
"BATTERY_CHARGING": "Şarj oluyor",
|
||||
"BATTERY_FULL": "Pil dolu",
|
||||
"BATTERY_NEED_CHARGE": "Pil düşük, lütfen şarj edin",
|
||||
"VOLUME": "Ses ",
|
||||
"MUTED": "Sessiz",
|
||||
"MAX_VOLUME": "Maksimum ses",
|
||||
"RTC_MODE_OFF": "AEC kapalı",
|
||||
"RTC_MODE_ON": "AEC açık",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Varlıklar indirilemedi",
|
||||
"LOADING_ASSETS": "Varlıklar yükleniyor...",
|
||||
"PLEASE_WAIT": "Lütfen bekleyin...",
|
||||
"FOUND_NEW_ASSETS": "Yeni varlıklar bulundu: %s",
|
||||
"HELLO_MY_FRIEND": "Merhaba, arkadaşım!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "tr-TR"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Uyarı",
|
||||
"INFO": "Bilgi",
|
||||
"ERROR": "Hata",
|
||||
"VERSION": "Sürüm ",
|
||||
"LOADING_PROTOCOL": "Sunucuya bağlanıyor...",
|
||||
"INITIALIZING": "Başlatılıyor...",
|
||||
"PIN_ERROR": "Lütfen SIM kartı takın",
|
||||
"REG_ERROR": "Ağa erişilemiyor, veri kartı durumunu kontrol edin",
|
||||
"DETECTING_MODULE": "Modül algılanıyor...",
|
||||
"REGISTERING_NETWORK": "Ağ bekleniyor...",
|
||||
"CHECKING_NEW_VERSION": "Yeni sürüm kontrol ediliyor...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Yeni sürüm kontrolü başarısız, %d saniye sonra tekrar denenecek: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Wi-Fi'ye geçiliyor...",
|
||||
"SWITCH_TO_4G_NETWORK": "4G'ye geçiliyor...",
|
||||
"STANDBY": "Bekleme",
|
||||
"CONNECT_TO": "Bağlan ",
|
||||
"CONNECTING": "Bağlanıyor...",
|
||||
"CONNECTED_TO": "Bağlandı ",
|
||||
"LISTENING": "Dinleniyor...",
|
||||
"SPEAKING": "Konuşuluyor...",
|
||||
"SERVER_NOT_FOUND": "Mevcut hizmet aranıyor",
|
||||
"SERVER_NOT_CONNECTED": "Hizmete bağlanılamıyor, lütfen daha sonra deneyin",
|
||||
"SERVER_TIMEOUT": "Yanıt zaman aşımı",
|
||||
"SERVER_ERROR": "Gönderme başarısız, ağı kontrol edin",
|
||||
"CONNECT_TO_HOTSPOT": "Telefonu hotspot'a bağlayın ",
|
||||
"ACCESS_VIA_BROWSER": ",tarayıcı üzerinden erişin ",
|
||||
"WIFI_CONFIG_MODE": "Ağ yapılandırma modu",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Ağ yapılandırma moduna giriliyor...",
|
||||
"SCANNING_WIFI": "Wi-Fi taranıyor...",
|
||||
"NEW_VERSION": "Yeni sürüm ",
|
||||
"OTA_UPGRADE": "OTA güncelleme",
|
||||
"UPGRADING": "Sistem güncelleniyor...",
|
||||
"UPGRADE_FAILED": "Güncelleme başarısız",
|
||||
"ACTIVATION": "Cihaz aktivasyonu",
|
||||
"BATTERY_LOW": "Pil düşük",
|
||||
"BATTERY_CHARGING": "Şarj oluyor",
|
||||
"BATTERY_FULL": "Pil dolu",
|
||||
"BATTERY_NEED_CHARGE": "Pil düşük, lütfen şarj edin",
|
||||
"VOLUME": "Ses ",
|
||||
"MUTED": "Sessiz",
|
||||
"MAX_VOLUME": "Maksimum ses",
|
||||
"RTC_MODE_OFF": "AEC kapalı",
|
||||
"RTC_MODE_ON": "AEC açık",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Varlıklar indirilemedi",
|
||||
"LOADING_ASSETS": "Varlıklar yükleniyor...",
|
||||
"PLEASE_WAIT": "Lütfen bekleyin...",
|
||||
"FOUND_NEW_ASSETS": "Yeni varlıklar bulundu: %s",
|
||||
"HELLO_MY_FRIEND": "Merhaba, arkadaşım!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "uk-UA"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Попередження",
|
||||
"INFO": "Інформація",
|
||||
"ERROR": "Помилка",
|
||||
"VERSION": "Версія ",
|
||||
"LOADING_PROTOCOL": "Підключення до сервера...",
|
||||
"INITIALIZING": "Ініціалізація...",
|
||||
"PIN_ERROR": "Будь ласка, вставте SIM-карту",
|
||||
"REG_ERROR": "Неможливо отримати доступ до мережі, перевірте стан карти даних",
|
||||
"DETECTING_MODULE": "Виявлення модуля...",
|
||||
"REGISTERING_NETWORK": "Очікування мережі...",
|
||||
"CHECKING_NEW_VERSION": "Перевірка нової версії...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Перевірка нової версії не вдалася, повтор через %d секунд: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Перемикання на Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Перемикання на 4G...",
|
||||
"STANDBY": "Очікування",
|
||||
"CONNECT_TO": "Підключитися до ",
|
||||
"CONNECTING": "Підключення...",
|
||||
"CONNECTED_TO": "Підключено до ",
|
||||
"LISTENING": "Прослуховування...",
|
||||
"SPEAKING": "Говоріння...",
|
||||
"SERVER_NOT_FOUND": "Пошук доступного сервісу",
|
||||
"SERVER_NOT_CONNECTED": "Неможливо підключитися до сервісу, спробуйте пізніше",
|
||||
"SERVER_TIMEOUT": "Час очікування відповіді",
|
||||
"SERVER_ERROR": "Помилка відправки, перевірте мережу",
|
||||
"CONNECT_TO_HOTSPOT": "Підключіть телефон до точки доступу ",
|
||||
"ACCESS_VIA_BROWSER": ",доступ через браузер ",
|
||||
"WIFI_CONFIG_MODE": "Режим налаштування мережі",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Вхід у режим налаштування мережі...",
|
||||
"SCANNING_WIFI": "Сканування Wi-Fi...",
|
||||
"NEW_VERSION": "Нова версія ",
|
||||
"OTA_UPGRADE": "Оновлення OTA",
|
||||
"UPGRADING": "Оновлення системи...",
|
||||
"UPGRADE_FAILED": "Оновлення не вдалося",
|
||||
"ACTIVATION": "Активація пристрою",
|
||||
"BATTERY_LOW": "Низький заряд батареї",
|
||||
"BATTERY_CHARGING": "Зарядка",
|
||||
"BATTERY_FULL": "Батарея повна",
|
||||
"BATTERY_NEED_CHARGE": "Низький заряд, будь ласка, зарядіть",
|
||||
"VOLUME": "Гучність ",
|
||||
"MUTED": "Звук вимкнено",
|
||||
"MAX_VOLUME": "Максимальна гучність",
|
||||
"RTC_MODE_OFF": "AEC вимкнено",
|
||||
"RTC_MODE_ON": "AEC увімкнено",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Не вдалося завантажити ресурси",
|
||||
"LOADING_ASSETS": "Завантаження ресурсів...",
|
||||
"PLEASE_WAIT": "Будь ласка, зачекайте...",
|
||||
"FOUND_NEW_ASSETS": "Знайдено нові ресурси: %s",
|
||||
"HELLO_MY_FRIEND": "Привіт, мій друже!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "uk-UA"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Попередження",
|
||||
"INFO": "Інформація",
|
||||
"ERROR": "Помилка",
|
||||
"VERSION": "Версія ",
|
||||
"LOADING_PROTOCOL": "Підключення до сервера...",
|
||||
"INITIALIZING": "Ініціалізація...",
|
||||
"PIN_ERROR": "Будь ласка, вставте SIM-карту",
|
||||
"REG_ERROR": "Неможливо отримати доступ до мережі, перевірте стан карти даних",
|
||||
"DETECTING_MODULE": "Виявлення модуля...",
|
||||
"REGISTERING_NETWORK": "Очікування мережі...",
|
||||
"CHECKING_NEW_VERSION": "Перевірка нової версії...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Перевірка нової версії не вдалася, повтор через %d секунд: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Перемикання на Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Перемикання на 4G...",
|
||||
"STANDBY": "Очікування",
|
||||
"CONNECT_TO": "Підключитися до ",
|
||||
"CONNECTING": "Підключення...",
|
||||
"CONNECTED_TO": "Підключено до ",
|
||||
"LISTENING": "Прослуховування...",
|
||||
"SPEAKING": "Говоріння...",
|
||||
"SERVER_NOT_FOUND": "Пошук доступного сервісу",
|
||||
"SERVER_NOT_CONNECTED": "Неможливо підключитися до сервісу, спробуйте пізніше",
|
||||
"SERVER_TIMEOUT": "Час очікування відповіді",
|
||||
"SERVER_ERROR": "Помилка відправки, перевірте мережу",
|
||||
"CONNECT_TO_HOTSPOT": "Підключіть телефон до точки доступу ",
|
||||
"ACCESS_VIA_BROWSER": ",доступ через браузер ",
|
||||
"WIFI_CONFIG_MODE": "Режим налаштування мережі",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Вхід у режим налаштування мережі...",
|
||||
"SCANNING_WIFI": "Сканування Wi-Fi...",
|
||||
"NEW_VERSION": "Нова версія ",
|
||||
"OTA_UPGRADE": "Оновлення OTA",
|
||||
"UPGRADING": "Оновлення системи...",
|
||||
"UPGRADE_FAILED": "Оновлення не вдалося",
|
||||
"ACTIVATION": "Активація пристрою",
|
||||
"BATTERY_LOW": "Низький заряд батареї",
|
||||
"BATTERY_CHARGING": "Зарядка",
|
||||
"BATTERY_FULL": "Батарея повна",
|
||||
"BATTERY_NEED_CHARGE": "Низький заряд, будь ласка, зарядіть",
|
||||
"VOLUME": "Гучність ",
|
||||
"MUTED": "Звук вимкнено",
|
||||
"MAX_VOLUME": "Максимальна гучність",
|
||||
"RTC_MODE_OFF": "AEC вимкнено",
|
||||
"RTC_MODE_ON": "AEC увімкнено",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Не вдалося завантажити ресурси",
|
||||
"LOADING_ASSETS": "Завантаження ресурсів...",
|
||||
"PLEASE_WAIT": "Будь ласка, зачекайте...",
|
||||
"FOUND_NEW_ASSETS": "Знайдено нові ресурси: %s",
|
||||
"HELLO_MY_FRIEND": "Привіт, мій друже!"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,56 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "vi-VN"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Cảnh báo",
|
||||
"INFO": "Thông tin",
|
||||
"ERROR": "Lỗi",
|
||||
"VERSION": "Phiên bản ",
|
||||
"LOADING_PROTOCOL": "Đang đăng nhập...",
|
||||
"INITIALIZING": "Đang khởi tạo...",
|
||||
"PIN_ERROR": "Vui lòng cắm thẻ SIM",
|
||||
"REG_ERROR": "Không thể truy cập mạng, vui lòng kiểm tra trạng thái thẻ SIM",
|
||||
"DETECTING_MODULE": "Đang phát hiện module...",
|
||||
"REGISTERING_NETWORK": "Đang chờ mạng...",
|
||||
"CHECKING_NEW_VERSION": "Đang kiểm tra phiên bản mới...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Kiểm tra phiên bản mới thất bại, sẽ thử lại sau %d giây: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Đang chuyển sang Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Đang chuyển sang 4G...",
|
||||
"STANDBY": "Chờ",
|
||||
"CONNECT_TO": "Kết nối đến ",
|
||||
"CONNECTING": "Đang kết nối...",
|
||||
"CONNECTION_SUCCESSFUL": "Kết nối thành công",
|
||||
"CONNECTED_TO": "Đã kết nối đến ",
|
||||
"LISTENING": "Đang lắng nghe...",
|
||||
"SPEAKING": "Đang nói...",
|
||||
"SERVER_NOT_FOUND": "Đang tìm dịch vụ khả dụng",
|
||||
"SERVER_NOT_CONNECTED": "Không thể kết nối đến dịch vụ, vui lòng thử lại sau",
|
||||
"SERVER_TIMEOUT": "Hết thời gian chờ phản hồi",
|
||||
"SERVER_ERROR": "Gửi thất bại, vui lòng kiểm tra mạng",
|
||||
"CONNECT_TO_HOTSPOT": "Điểm phát sóng: ",
|
||||
"ACCESS_VIA_BROWSER": " URL cấu hình: ",
|
||||
"WIFI_CONFIG_MODE": "Chế độ cấu hình Wi-Fi",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Đang vào chế độ cấu hình Wi-Fi...",
|
||||
"SCANNING_WIFI": "Đang quét Wi-Fi...",
|
||||
"NEW_VERSION": "Phiên bản mới ",
|
||||
"OTA_UPGRADE": "Nâng cấp OTA",
|
||||
"UPGRADING": "Hệ thống đang nâng cấp...",
|
||||
"UPGRADE_FAILED": "Nâng cấp thất bại",
|
||||
"ACTIVATION": "Kích hoạt",
|
||||
"BATTERY_LOW": "Pin yếu",
|
||||
"BATTERY_CHARGING": "Đang sạc",
|
||||
"BATTERY_FULL": "Pin đầy",
|
||||
"BATTERY_NEED_CHARGE": "Pin yếu, vui lòng sạc",
|
||||
"VOLUME": "Âm lượng ",
|
||||
"MUTED": "Tắt tiếng",
|
||||
"MAX_VOLUME": "Âm lượng tối đa",
|
||||
"RTC_MODE_OFF": "Tắt AEC",
|
||||
"RTC_MODE_ON": "Bật AEC",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Tải xuống tài nguyên thất bại",
|
||||
"LOADING_ASSETS": "Đang tải tài nguyên...",
|
||||
"PLEASE_WAIT": "Vui lòng đợi...",
|
||||
"FOUND_NEW_ASSETS": "Tìm thấy tài nguyên mới: %s",
|
||||
"HELLO_MY_FRIEND": "Xin chào, bạn của tôi!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "vi-VN"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "Cảnh báo",
|
||||
"INFO": "Thông tin",
|
||||
"ERROR": "Lỗi",
|
||||
"VERSION": "Phiên bản ",
|
||||
"LOADING_PROTOCOL": "Đang đăng nhập...",
|
||||
"INITIALIZING": "Đang khởi tạo...",
|
||||
"PIN_ERROR": "Vui lòng cắm thẻ SIM",
|
||||
"REG_ERROR": "Không thể truy cập mạng, vui lòng kiểm tra trạng thái thẻ SIM",
|
||||
"DETECTING_MODULE": "Đang phát hiện module...",
|
||||
"REGISTERING_NETWORK": "Đang chờ mạng...",
|
||||
"CHECKING_NEW_VERSION": "Đang kiểm tra phiên bản mới...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Kiểm tra phiên bản mới thất bại, sẽ thử lại sau %d giây: %s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "Đang chuyển sang Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "Đang chuyển sang 4G...",
|
||||
"STANDBY": "Chờ",
|
||||
"CONNECT_TO": "Kết nối đến ",
|
||||
"CONNECTING": "Đang kết nối...",
|
||||
"CONNECTION_SUCCESSFUL": "Kết nối thành công",
|
||||
"CONNECTED_TO": "Đã kết nối đến ",
|
||||
"LISTENING": "Đang lắng nghe...",
|
||||
"SPEAKING": "Đang nói...",
|
||||
"SERVER_NOT_FOUND": "Đang tìm dịch vụ khả dụng",
|
||||
"SERVER_NOT_CONNECTED": "Không thể kết nối đến dịch vụ, vui lòng thử lại sau",
|
||||
"SERVER_TIMEOUT": "Hết thời gian chờ phản hồi",
|
||||
"SERVER_ERROR": "Gửi thất bại, vui lòng kiểm tra mạng",
|
||||
"CONNECT_TO_HOTSPOT": "Điểm phát sóng: ",
|
||||
"ACCESS_VIA_BROWSER": " URL cấu hình: ",
|
||||
"WIFI_CONFIG_MODE": "Chế độ cấu hình Wi-Fi",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "Đang vào chế độ cấu hình Wi-Fi...",
|
||||
"SCANNING_WIFI": "Đang quét Wi-Fi...",
|
||||
"NEW_VERSION": "Phiên bản mới ",
|
||||
"OTA_UPGRADE": "Nâng cấp OTA",
|
||||
"UPGRADING": "Hệ thống đang nâng cấp...",
|
||||
"UPGRADE_FAILED": "Nâng cấp thất bại",
|
||||
"ACTIVATION": "Kích hoạt",
|
||||
"BATTERY_LOW": "Pin yếu",
|
||||
"BATTERY_CHARGING": "Đang sạc",
|
||||
"BATTERY_FULL": "Pin đầy",
|
||||
"BATTERY_NEED_CHARGE": "Pin yếu, vui lòng sạc",
|
||||
"VOLUME": "Âm lượng ",
|
||||
"MUTED": "Tắt tiếng",
|
||||
"MAX_VOLUME": "Âm lượng tối đa",
|
||||
"RTC_MODE_OFF": "Tắt AEC",
|
||||
"RTC_MODE_ON": "Bật AEC",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Tải xuống tài nguyên thất bại",
|
||||
"LOADING_ASSETS": "Đang tải tài nguyên...",
|
||||
"PLEASE_WAIT": "Vui lòng đợi...",
|
||||
"FOUND_NEW_ASSETS": "Tìm thấy tài nguyên mới: %s",
|
||||
"HELLO_MY_FRIEND": "Xin chào, bạn của tôi!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "zh-CN"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "警告",
|
||||
"INFO": "信息",
|
||||
"ERROR": "错误",
|
||||
"VERSION": "版本 ",
|
||||
"LOADING_PROTOCOL": "登录服务器...",
|
||||
"INITIALIZING": "正在初始化...",
|
||||
"PIN_ERROR": "请插入 SIM 卡",
|
||||
"REG_ERROR": "无法接入网络,请检查流量卡状态",
|
||||
"DETECTING_MODULE": "检测模组...",
|
||||
"REGISTERING_NETWORK": "等待网络...",
|
||||
"CHECKING_NEW_VERSION": "检查新版本...",
|
||||
"CHECK_NEW_VERSION_FAILED": "检查新版本失败,将在 %d 秒后重试:%s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "切换到 Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "切换到 4G...",
|
||||
"STANDBY": "待命",
|
||||
"CONNECT_TO": "连接 ",
|
||||
"CONNECTING": "连接中...",
|
||||
"CONNECTED_TO": "已连接 ",
|
||||
"LISTENING": "聆听中...",
|
||||
"SPEAKING": "说话中...",
|
||||
"SERVER_NOT_FOUND": "正在寻找可用服务",
|
||||
"SERVER_NOT_CONNECTED": "无法连接服务,请稍后再试",
|
||||
"SERVER_TIMEOUT": "等待响应超时",
|
||||
"SERVER_ERROR": "发送失败,请检查网络",
|
||||
"CONNECT_TO_HOTSPOT": "手机连接热点 ",
|
||||
"ACCESS_VIA_BROWSER": ",浏览器访问 ",
|
||||
"WIFI_CONFIG_MODE": "配网模式",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "进入配网模式...",
|
||||
"SCANNING_WIFI": "扫描 Wi-Fi...",
|
||||
"NEW_VERSION": "新版本 ",
|
||||
"OTA_UPGRADE": "OTA 升级",
|
||||
"UPGRADING": "正在升级系统...",
|
||||
"UPGRADE_FAILED": "升级失败",
|
||||
"ACTIVATION": "激活设备",
|
||||
"BATTERY_LOW": "电量不足",
|
||||
"BATTERY_CHARGING": "正在充电",
|
||||
"BATTERY_FULL": "电量已满",
|
||||
"BATTERY_NEED_CHARGE": "电量低,请充电",
|
||||
"VOLUME": "音量 ",
|
||||
"MUTED": "已静音",
|
||||
"MAX_VOLUME": "最大音量",
|
||||
"RTC_MODE_OFF": "AEC 关闭",
|
||||
"RTC_MODE_ON": "AEC 开启",
|
||||
"DOWNLOAD_ASSETS_FAILED": "下载资源失败",
|
||||
"LOADING_ASSETS": "加载资源...",
|
||||
"PLEASE_WAIT": "请稍候...",
|
||||
"FOUND_NEW_ASSETS": "发现新资源: %s",
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "zh-CN"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "警告",
|
||||
"INFO": "信息",
|
||||
"ERROR": "错误",
|
||||
"VERSION": "版本 ",
|
||||
"LOADING_PROTOCOL": "登录服务器...",
|
||||
"INITIALIZING": "正在初始化...",
|
||||
"PIN_ERROR": "请插入 SIM 卡",
|
||||
"REG_ERROR": "无法接入网络,请检查流量卡状态",
|
||||
"DETECTING_MODULE": "检测模组...",
|
||||
"REGISTERING_NETWORK": "等待网络...",
|
||||
"CHECKING_NEW_VERSION": "检查新版本...",
|
||||
"CHECK_NEW_VERSION_FAILED": "检查新版本失败,将在 %d 秒后重试:%s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "切换到 Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "切换到 4G...",
|
||||
"STANDBY": "待命",
|
||||
"CONNECT_TO": "连接 ",
|
||||
"CONNECTING": "连接中...",
|
||||
"CONNECTED_TO": "已连接 ",
|
||||
"LISTENING": "聆听中...",
|
||||
"SPEAKING": "说话中...",
|
||||
"SERVER_NOT_FOUND": "正在寻找可用服务",
|
||||
"SERVER_NOT_CONNECTED": "无法连接服务,请稍后再试",
|
||||
"SERVER_TIMEOUT": "等待响应超时",
|
||||
"SERVER_ERROR": "发送失败,请检查网络",
|
||||
"CONNECT_TO_HOTSPOT": "手机连接热点 ",
|
||||
"ACCESS_VIA_BROWSER": ",浏览器访问 ",
|
||||
"WIFI_CONFIG_MODE": "配网模式",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "进入配网模式...",
|
||||
"SCANNING_WIFI": "扫描 Wi-Fi...",
|
||||
"NEW_VERSION": "新版本 ",
|
||||
"OTA_UPGRADE": "OTA 升级",
|
||||
"UPGRADING": "正在升级系统...",
|
||||
"UPGRADE_FAILED": "升级失败",
|
||||
"ACTIVATION": "激活设备",
|
||||
"BATTERY_LOW": "电量不足",
|
||||
"BATTERY_CHARGING": "正在充电",
|
||||
"BATTERY_FULL": "电量已满",
|
||||
"BATTERY_NEED_CHARGE": "电量低,请充电",
|
||||
"VOLUME": "音量 ",
|
||||
"MUTED": "已静音",
|
||||
"MAX_VOLUME": "最大音量",
|
||||
"RTC_MODE_OFF": "AEC 关闭",
|
||||
"RTC_MODE_ON": "AEC 开启",
|
||||
"DOWNLOAD_ASSETS_FAILED": "下载资源失败",
|
||||
"LOADING_ASSETS": "加载资源...",
|
||||
"PLEASE_WAIT": "请稍候...",
|
||||
"FOUND_NEW_ASSETS": "发现新资源: %s",
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!"
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
{
|
||||
"language": {
|
||||
"type": "zh-TW"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "警告",
|
||||
"INFO": "資訊",
|
||||
"ERROR": "錯誤",
|
||||
"VERSION": "版本 ",
|
||||
"LOADING_PROTOCOL": "登入伺服器...",
|
||||
"INITIALIZING": "正在初始化...",
|
||||
"PIN_ERROR": "請插入 SIM 卡",
|
||||
"REG_ERROR": "無法接入網絡,請檢查網路狀態",
|
||||
"DETECTING_MODULE": "檢測模組...",
|
||||
"REGISTERING_NETWORK": "等待網絡...",
|
||||
"CHECKING_NEW_VERSION": "檢查新版本...",
|
||||
"CHECK_NEW_VERSION_FAILED": "檢查新版本失敗,將在 %d 秒後重試:%s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "切換到 Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "切換到 4G...",
|
||||
"STANDBY": "待命",
|
||||
"CONNECT_TO": "連接 ",
|
||||
"CONNECTING": "連接中...",
|
||||
"CONNECTED_TO": "已連接 ",
|
||||
"LISTENING": "聆聽中...",
|
||||
"SPEAKING": "說話中...",
|
||||
"SERVER_NOT_FOUND": "正在尋找可用服務",
|
||||
"SERVER_NOT_CONNECTED": "無法連接服務,請稍後再試",
|
||||
"SERVER_TIMEOUT": "等待響應超時",
|
||||
"SERVER_ERROR": "發送失敗,請檢查網絡",
|
||||
"CONNECT_TO_HOTSPOT": "手機連接WiFi ",
|
||||
"ACCESS_VIA_BROWSER": ",瀏覽器訪問 ",
|
||||
"WIFI_CONFIG_MODE": "網路設定模式",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "正在設定網路...",
|
||||
"SCANNING_WIFI": "掃描 Wi-Fi...",
|
||||
"NEW_VERSION": "新版本 ",
|
||||
"OTA_UPGRADE": "OTA 升級",
|
||||
"UPGRADING": "正在升級系統...",
|
||||
"UPGRADE_FAILED": "升級失敗",
|
||||
"ACTIVATION": "啟用設備",
|
||||
"BATTERY_LOW": "電量不足",
|
||||
"BATTERY_CHARGING": "正在充電",
|
||||
"BATTERY_FULL": "電量已滿",
|
||||
"BATTERY_NEED_CHARGE": "電量低,請充電",
|
||||
"VOLUME": "音量 ",
|
||||
"MUTED": "已靜音",
|
||||
"MAX_VOLUME": "最大音量",
|
||||
"RTC_MODE_OFF": "AEC 關閉",
|
||||
"RTC_MODE_ON": "AEC 開啟",
|
||||
"DOWNLOAD_ASSETS_FAILED": "下載資源失敗",
|
||||
"LOADING_ASSETS": "載入資源...",
|
||||
"PLEASE_WAIT": "請稍候...",
|
||||
"FOUND_NEW_ASSETS": "發現新資源: %s",
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!"
|
||||
}
|
||||
{
|
||||
"language": {
|
||||
"type": "zh-TW"
|
||||
},
|
||||
"strings": {
|
||||
"WARNING": "警告",
|
||||
"INFO": "資訊",
|
||||
"ERROR": "錯誤",
|
||||
"VERSION": "版本 ",
|
||||
"LOADING_PROTOCOL": "登入伺服器...",
|
||||
"INITIALIZING": "正在初始化...",
|
||||
"PIN_ERROR": "請插入 SIM 卡",
|
||||
"REG_ERROR": "無法接入網絡,請檢查網路狀態",
|
||||
"DETECTING_MODULE": "檢測模組...",
|
||||
"REGISTERING_NETWORK": "等待網絡...",
|
||||
"CHECKING_NEW_VERSION": "檢查新版本...",
|
||||
"CHECK_NEW_VERSION_FAILED": "檢查新版本失敗,將在 %d 秒後重試:%s",
|
||||
"SWITCH_TO_WIFI_NETWORK": "切換到 Wi-Fi...",
|
||||
"SWITCH_TO_4G_NETWORK": "切換到 4G...",
|
||||
"STANDBY": "待命",
|
||||
"CONNECT_TO": "連接 ",
|
||||
"CONNECTING": "連接中...",
|
||||
"CONNECTED_TO": "已連接 ",
|
||||
"LISTENING": "聆聽中...",
|
||||
"SPEAKING": "說話中...",
|
||||
"SERVER_NOT_FOUND": "正在尋找可用服務",
|
||||
"SERVER_NOT_CONNECTED": "無法連接服務,請稍後再試",
|
||||
"SERVER_TIMEOUT": "等待響應超時",
|
||||
"SERVER_ERROR": "發送失敗,請檢查網絡",
|
||||
"CONNECT_TO_HOTSPOT": "手機連接WiFi ",
|
||||
"ACCESS_VIA_BROWSER": ",瀏覽器訪問 ",
|
||||
"WIFI_CONFIG_MODE": "網路設定模式",
|
||||
"ENTERING_WIFI_CONFIG_MODE": "正在設定網路...",
|
||||
"SCANNING_WIFI": "掃描 Wi-Fi...",
|
||||
"NEW_VERSION": "新版本 ",
|
||||
"OTA_UPGRADE": "OTA 升級",
|
||||
"UPGRADING": "正在升級系統...",
|
||||
"UPGRADE_FAILED": "升級失敗",
|
||||
"ACTIVATION": "啟用設備",
|
||||
"BATTERY_LOW": "電量不足",
|
||||
"BATTERY_CHARGING": "正在充電",
|
||||
"BATTERY_FULL": "電量已滿",
|
||||
"BATTERY_NEED_CHARGE": "電量低,請充電",
|
||||
"VOLUME": "音量 ",
|
||||
"MUTED": "已靜音",
|
||||
"MAX_VOLUME": "最大音量",
|
||||
"RTC_MODE_OFF": "AEC 關閉",
|
||||
"RTC_MODE_ON": "AEC 開啟",
|
||||
"DOWNLOAD_ASSETS_FAILED": "下載資源失敗",
|
||||
"LOADING_ASSETS": "載入資源...",
|
||||
"PLEASE_WAIT": "請稍候...",
|
||||
"FOUND_NEW_ASSETS": "發現新資源: %s",
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!"
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,88 @@
|
||||
# Audio Service Architecture
|
||||
|
||||
The audio service is a core component responsible for managing all audio-related functionalities, including capturing audio from the microphone, processing it, encoding/decoding, and playing back audio through the speaker. It is designed to be modular and efficient, running its main operations in dedicated FreeRTOS tasks to ensure real-time performance.
|
||||
|
||||
## Key Components
|
||||
|
||||
- **`AudioService`**: The central orchestrator. It initializes and manages all other audio components, tasks, and data queues.
|
||||
- **`AudioCodec`**: A hardware abstraction layer (HAL) for the physical audio codec chip. It handles the raw I2S communication for audio input and output.
|
||||
- **`AudioProcessor`**: Performs real-time audio processing on the microphone input stream. This typically includes Acoustic Echo Cancellation (AEC), noise suppression, and Voice Activity Detection (VAD). `AfeAudioProcessor` is the default implementation, utilizing the ESP-ADF Audio Front-End.
|
||||
- **`WakeWord`**: Detects keywords (e.g., "你好,小智", "Hi, ESP") from the audio stream. It runs independently from the main audio processor until a wake word is detected.
|
||||
- **`OpusEncoderWrapper` / `OpusDecoderWrapper`**: Manages the encoding of PCM audio to the Opus format and decoding Opus packets back to PCM. Opus is used for its high compression and low latency, making it ideal for voice streaming.
|
||||
- **`OpusResampler`**: A utility to convert audio streams between different sample rates (e.g., resampling from the codec's native sample rate to the required 16kHz for processing).
|
||||
|
||||
## Threading Model
|
||||
|
||||
The service operates on three primary tasks to handle the different stages of the audio pipeline concurrently:
|
||||
|
||||
1. **`AudioInputTask`**: Solely responsible for reading raw PCM data from the `AudioCodec`. It then feeds this data to either the `WakeWord` engine or the `AudioProcessor` based on the current state.
|
||||
2. **`AudioOutputTask`**: Responsible for playing audio. It retrieves decoded PCM data from the `audio_playback_queue_` and sends it to the `AudioCodec` to be played on the speaker.
|
||||
3. **`OpusCodecTask`**: A worker task that handles both encoding and decoding. It fetches raw audio from `audio_encode_queue_`, encodes it into Opus packets, and places them in the `audio_send_queue_`. Concurrently, it fetches Opus packets from `audio_decode_queue_`, decodes them into PCM, and places the result in the `audio_playback_queue_`.
|
||||
|
||||
## Data Flow
|
||||
|
||||
There are two primary data flows: audio input (uplink) and audio output (downlink).
|
||||
|
||||
### 1. Audio Input (Uplink) Flow
|
||||
|
||||
This flow captures audio from the microphone, processes it, encodes it, and prepares it for sending to a server.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Device
|
||||
Mic[("Microphone")] -->|I2S| Codec(AudioCodec)
|
||||
|
||||
subgraph AudioInputTask
|
||||
Codec -->|Raw PCM| Read(ReadAudioData)
|
||||
Read -->|16kHz PCM| Processor(AudioProcessor)
|
||||
end
|
||||
|
||||
subgraph OpusCodecTask
|
||||
Processor -->|Clean PCM| EncodeQueue(audio_encode_queue_)
|
||||
EncodeQueue --> Encoder(OpusEncoder)
|
||||
Encoder -->|Opus Packet| SendQueue(audio_send_queue_)
|
||||
end
|
||||
|
||||
SendQueue --> |"PopPacketFromSendQueue()"| App(Application Layer)
|
||||
end
|
||||
|
||||
App -->|Network| Server((Cloud Server))
|
||||
```
|
||||
|
||||
- The `AudioInputTask` continuously reads raw PCM data from the `AudioCodec`.
|
||||
- This data is fed into an `AudioProcessor` for cleaning (AEC, VAD).
|
||||
- The processed PCM data is pushed into the `audio_encode_queue_`.
|
||||
- The `OpusCodecTask` picks up the PCM data, encodes it into Opus format, and pushes the resulting packet to the `audio_send_queue_`.
|
||||
- The application can then retrieve these Opus packets and send them over the network.
|
||||
|
||||
### 2. Audio Output (Downlink) Flow
|
||||
|
||||
This flow receives encoded audio data, decodes it, and plays it on the speaker.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Server((Cloud Server)) -->|Network| App(Application Layer)
|
||||
|
||||
subgraph Device
|
||||
App -->|"PushPacketToDecodeQueue()"| DecodeQueue(audio_decode_queue_)
|
||||
|
||||
subgraph OpusCodecTask
|
||||
DecodeQueue -->|Opus Packet| Decoder(OpusDecoder)
|
||||
Decoder -->|PCM| PlaybackQueue(audio_playback_queue_)
|
||||
end
|
||||
|
||||
subgraph AudioOutputTask
|
||||
PlaybackQueue -->|PCM| Codec(AudioCodec)
|
||||
end
|
||||
|
||||
Codec -->|I2S| Speaker[("Speaker")]
|
||||
end
|
||||
```
|
||||
|
||||
- The application receives Opus packets from the network and pushes them into the `audio_decode_queue_`.
|
||||
- The `OpusCodecTask` retrieves these packets, decodes them back into PCM data, and pushes the data to the `audio_playback_queue_`.
|
||||
- The `AudioOutputTask` takes the PCM data from the queue and sends it to the `AudioCodec` for playback.
|
||||
|
||||
## Power Management
|
||||
|
||||
# Audio Service Architecture
|
||||
|
||||
The audio service is a core component responsible for managing all audio-related functionalities, including capturing audio from the microphone, processing it, encoding/decoding, and playing back audio through the speaker. It is designed to be modular and efficient, running its main operations in dedicated FreeRTOS tasks to ensure real-time performance.
|
||||
|
||||
## Key Components
|
||||
|
||||
- **`AudioService`**: The central orchestrator. It initializes and manages all other audio components, tasks, and data queues.
|
||||
- **`AudioCodec`**: A hardware abstraction layer (HAL) for the physical audio codec chip. It handles the raw I2S communication for audio input and output.
|
||||
- **`AudioProcessor`**: Performs real-time audio processing on the microphone input stream. This typically includes Acoustic Echo Cancellation (AEC), noise suppression, and Voice Activity Detection (VAD). `AfeAudioProcessor` is the default implementation, utilizing the ESP-ADF Audio Front-End.
|
||||
- **`WakeWord`**: Detects keywords (e.g., "你好,小智", "Hi, ESP") from the audio stream. It runs independently from the main audio processor until a wake word is detected.
|
||||
- **`OpusEncoderWrapper` / `OpusDecoderWrapper`**: Manages the encoding of PCM audio to the Opus format and decoding Opus packets back to PCM. Opus is used for its high compression and low latency, making it ideal for voice streaming.
|
||||
- **`OpusResampler`**: A utility to convert audio streams between different sample rates (e.g., resampling from the codec's native sample rate to the required 16kHz for processing).
|
||||
|
||||
## Threading Model
|
||||
|
||||
The service operates on three primary tasks to handle the different stages of the audio pipeline concurrently:
|
||||
|
||||
1. **`AudioInputTask`**: Solely responsible for reading raw PCM data from the `AudioCodec`. It then feeds this data to either the `WakeWord` engine or the `AudioProcessor` based on the current state.
|
||||
2. **`AudioOutputTask`**: Responsible for playing audio. It retrieves decoded PCM data from the `audio_playback_queue_` and sends it to the `AudioCodec` to be played on the speaker.
|
||||
3. **`OpusCodecTask`**: A worker task that handles both encoding and decoding. It fetches raw audio from `audio_encode_queue_`, encodes it into Opus packets, and places them in the `audio_send_queue_`. Concurrently, it fetches Opus packets from `audio_decode_queue_`, decodes them into PCM, and places the result in the `audio_playback_queue_`.
|
||||
|
||||
## Data Flow
|
||||
|
||||
There are two primary data flows: audio input (uplink) and audio output (downlink).
|
||||
|
||||
### 1. Audio Input (Uplink) Flow
|
||||
|
||||
This flow captures audio from the microphone, processes it, encodes it, and prepares it for sending to a server.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Device
|
||||
Mic[("Microphone")] -->|I2S| Codec(AudioCodec)
|
||||
|
||||
subgraph AudioInputTask
|
||||
Codec -->|Raw PCM| Read(ReadAudioData)
|
||||
Read -->|16kHz PCM| Processor(AudioProcessor)
|
||||
end
|
||||
|
||||
subgraph OpusCodecTask
|
||||
Processor -->|Clean PCM| EncodeQueue(audio_encode_queue_)
|
||||
EncodeQueue --> Encoder(OpusEncoder)
|
||||
Encoder -->|Opus Packet| SendQueue(audio_send_queue_)
|
||||
end
|
||||
|
||||
SendQueue --> |"PopPacketFromSendQueue()"| App(Application Layer)
|
||||
end
|
||||
|
||||
App -->|Network| Server((Cloud Server))
|
||||
```
|
||||
|
||||
- The `AudioInputTask` continuously reads raw PCM data from the `AudioCodec`.
|
||||
- This data is fed into an `AudioProcessor` for cleaning (AEC, VAD).
|
||||
- The processed PCM data is pushed into the `audio_encode_queue_`.
|
||||
- The `OpusCodecTask` picks up the PCM data, encodes it into Opus format, and pushes the resulting packet to the `audio_send_queue_`.
|
||||
- The application can then retrieve these Opus packets and send them over the network.
|
||||
|
||||
### 2. Audio Output (Downlink) Flow
|
||||
|
||||
This flow receives encoded audio data, decodes it, and plays it on the speaker.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Server((Cloud Server)) -->|Network| App(Application Layer)
|
||||
|
||||
subgraph Device
|
||||
App -->|"PushPacketToDecodeQueue()"| DecodeQueue(audio_decode_queue_)
|
||||
|
||||
subgraph OpusCodecTask
|
||||
DecodeQueue -->|Opus Packet| Decoder(OpusDecoder)
|
||||
Decoder -->|PCM| PlaybackQueue(audio_playback_queue_)
|
||||
end
|
||||
|
||||
subgraph AudioOutputTask
|
||||
PlaybackQueue -->|PCM| Codec(AudioCodec)
|
||||
end
|
||||
|
||||
Codec -->|I2S| Speaker[("Speaker")]
|
||||
end
|
||||
```
|
||||
|
||||
- The application receives Opus packets from the network and pushes them into the `audio_decode_queue_`.
|
||||
- The `OpusCodecTask` retrieves these packets, decodes them back into PCM data, and pushes the data to the `audio_playback_queue_`.
|
||||
- The `AudioOutputTask` takes the PCM data from the queue and sends it to the `AudioCodec` for playback.
|
||||
|
||||
## Power Management
|
||||
|
||||
To conserve energy, the audio codec's input (ADC) and output (DAC) channels are automatically disabled after a period of inactivity (`AUDIO_POWER_TIMEOUT_MS`). A timer (`audio_power_timer_`) periodically checks for activity and manages the power state. The channels are automatically re-enabled when new audio needs to be captured or played.
|
||||
@@ -1,162 +1,149 @@
|
||||
#include "audio_codec.h"
|
||||
#include "board.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
#include <driver/i2s_common.h>
|
||||
|
||||
#define TAG "AudioCodec"
|
||||
|
||||
AudioCodec::AudioCodec() {
|
||||
}
|
||||
|
||||
AudioCodec::~AudioCodec() {
|
||||
}
|
||||
|
||||
void AudioCodec::OutputData(std::vector<int16_t>& data) {
|
||||
Write(data.data(), data.size());
|
||||
}
|
||||
|
||||
bool AudioCodec::InputData(std::vector<int16_t>& data) {
|
||||
int samples = Read(data.data(), data.size());
|
||||
if (samples > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioCodec::Start() {
|
||||
Settings settings("audio", false);
|
||||
output_volume_ = settings.GetInt("output_volume", output_volume_);
|
||||
if (output_volume_ <= 0) {
|
||||
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
|
||||
output_volume_ = 10;
|
||||
}
|
||||
|
||||
// 保存原始输出采样率
|
||||
if (original_output_sample_rate_ == 0) {
|
||||
original_output_sample_rate_ = output_sample_rate_;
|
||||
ESP_LOGI(TAG, "Saved original output sample rate: %d Hz", original_output_sample_rate_);
|
||||
}
|
||||
|
||||
if (tx_handle_ != nullptr) {
|
||||
esp_err_t err = i2s_channel_enable(tx_handle_);
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
// 已经启用,忽略
|
||||
ESP_LOGW(TAG, "TX channel already enabled");
|
||||
} else {
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_handle_ != nullptr) {
|
||||
esp_err_t err = i2s_channel_enable(rx_handle_);
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGW(TAG, "RX channel already enabled");
|
||||
} else {
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
}
|
||||
|
||||
EnableInput(true);
|
||||
EnableOutput(true);
|
||||
ESP_LOGI(TAG, "Audio codec started");
|
||||
}
|
||||
|
||||
void AudioCodec::SetOutputVolume(int volume) {
|
||||
output_volume_ = volume;
|
||||
ESP_LOGI(TAG, "Set output volume to %d", output_volume_);
|
||||
|
||||
Settings settings("audio", true);
|
||||
settings.SetInt("output_volume", 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");
|
||||
}
|
||||
|
||||
bool AudioCodec::SetOutputSampleRate(int sample_rate) {
|
||||
// 特殊处理:如果传入 -1,表示重置到原始采样率
|
||||
if (sample_rate == -1) {
|
||||
if (original_output_sample_rate_ > 0) {
|
||||
sample_rate = original_output_sample_rate_;
|
||||
ESP_LOGI(TAG, "Resetting to original output sample rate: %d Hz", sample_rate);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Original sample rate not available, cannot reset");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_rate <= 0 || sample_rate > 192000) {
|
||||
ESP_LOGE(TAG, "Invalid sample rate: %d", sample_rate);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output_sample_rate_ == sample_rate) {
|
||||
ESP_LOGI(TAG, "Sample rate already set to %d Hz", sample_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tx_handle_ == nullptr) {
|
||||
ESP_LOGW(TAG, "TX handle is null, only updating sample rate variable");
|
||||
output_sample_rate_ = sample_rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Changing output sample rate from %d to %d Hz", output_sample_rate_, sample_rate);
|
||||
|
||||
// 先尝试禁用 I2S 通道(如果已启用的话)
|
||||
bool was_enabled = false;
|
||||
esp_err_t disable_ret = i2s_channel_disable(tx_handle_);
|
||||
if (disable_ret == ESP_OK) {
|
||||
was_enabled = true;
|
||||
ESP_LOGI(TAG, "Disabled I2S TX channel for reconfiguration");
|
||||
} else if (disable_ret == ESP_ERR_INVALID_STATE) {
|
||||
// 通道可能已经是禁用状态,这是正常的
|
||||
ESP_LOGI(TAG, "I2S TX channel was already disabled");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to disable I2S TX channel: %s", esp_err_to_name(disable_ret));
|
||||
}
|
||||
|
||||
// 重新配置 I2S 时钟
|
||||
i2s_std_clk_config_t clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)sample_rate,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
esp_err_t ret = i2s_channel_reconfig_std_clock(tx_handle_, &clk_cfg);
|
||||
|
||||
// 重新启用通道(无论之前是什么状态,现在都需要启用以便播放音频)
|
||||
esp_err_t enable_ret = i2s_channel_enable(tx_handle_);
|
||||
if (enable_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enable I2S TX channel: %s", esp_err_to_name(enable_ret));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Enabled I2S TX channel");
|
||||
}
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
output_sample_rate_ = sample_rate;
|
||||
ESP_LOGI(TAG, "Successfully changed output sample rate to %d Hz", sample_rate);
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to change sample rate to %d Hz: %s", sample_rate, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
#include "audio_codec.h"
|
||||
#include "board.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
#include <driver/i2s_common.h>
|
||||
|
||||
#define TAG "AudioCodec"
|
||||
|
||||
AudioCodec::AudioCodec() {
|
||||
}
|
||||
|
||||
AudioCodec::~AudioCodec() {
|
||||
}
|
||||
|
||||
void AudioCodec::OutputData(std::vector<int16_t>& data) {
|
||||
Write(data.data(), data.size());
|
||||
}
|
||||
|
||||
bool AudioCodec::InputData(std::vector<int16_t>& data) {
|
||||
int samples = Read(data.data(), data.size());
|
||||
if (samples > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioCodec::Start() {
|
||||
Settings settings("audio", false);
|
||||
output_volume_ = settings.GetInt("output_volume", output_volume_);
|
||||
if (output_volume_ <= 0) {
|
||||
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
|
||||
output_volume_ = 10;
|
||||
}
|
||||
// 保存原始输出采样率
|
||||
if (original_output_sample_rate_ == 0){
|
||||
original_output_sample_rate_ = output_sample_rate_;
|
||||
ESP_LOGI(TAG, "Saved original output sample rate: %d Hz", original_output_sample_rate_);
|
||||
}
|
||||
if (tx_handle_ != nullptr) {
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
|
||||
}
|
||||
|
||||
if (rx_handle_ != nullptr) {
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
|
||||
}
|
||||
|
||||
EnableInput(true);
|
||||
EnableOutput(true);
|
||||
ESP_LOGI(TAG, "Audio codec started");
|
||||
}
|
||||
|
||||
void AudioCodec::SetOutputVolume(int volume) {
|
||||
output_volume_ = volume;
|
||||
ESP_LOGI(TAG, "Set output volume to %d", output_volume_);
|
||||
|
||||
Settings settings("audio", true);
|
||||
settings.SetInt("output_volume", 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");
|
||||
}
|
||||
|
||||
bool AudioCodec::SetOutputSampleRate(int sample_rate) {
|
||||
// 特殊处理:如果传入 -1,表示重置到原始采样率
|
||||
if (sample_rate == -1) {
|
||||
if (original_output_sample_rate_ > 0) {
|
||||
sample_rate = original_output_sample_rate_;
|
||||
ESP_LOGI(TAG, "Resetting to original output sample rate: %d Hz", sample_rate);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Original sample rate not available, cannot reset");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_rate <= 0 || sample_rate > 192000) {
|
||||
ESP_LOGE(TAG, "Invalid sample rate: %d", sample_rate);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output_sample_rate_ == sample_rate) {
|
||||
ESP_LOGI(TAG, "Sample rate already set to %d Hz", sample_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tx_handle_ == nullptr) {
|
||||
ESP_LOGW(TAG, "TX handle is null, only updating sample rate variable");
|
||||
output_sample_rate_ = sample_rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Changing output sample rate from %d to %d Hz", output_sample_rate_, sample_rate);
|
||||
|
||||
// 先尝试禁用 I2S 通道(如果已启用的话)
|
||||
bool was_enabled = false;
|
||||
esp_err_t disable_ret = i2s_channel_disable(tx_handle_);
|
||||
if (disable_ret == ESP_OK) {
|
||||
was_enabled = true;
|
||||
ESP_LOGI(TAG, "Disabled I2S TX channel for reconfiguration");
|
||||
} else if (disable_ret == ESP_ERR_INVALID_STATE) {
|
||||
// 通道可能已经是禁用状态,这是正常的
|
||||
ESP_LOGI(TAG, "I2S TX channel was already disabled");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to disable I2S TX channel: %s", esp_err_to_name(disable_ret));
|
||||
}
|
||||
|
||||
// 重新配置 I2S 时钟
|
||||
i2s_std_clk_config_t clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)sample_rate,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
esp_err_t ret = i2s_channel_reconfig_std_clock(tx_handle_, &clk_cfg);
|
||||
|
||||
// 重新启用通道(无论之前是什么状态,现在都需要启用以便播放音频)
|
||||
esp_err_t enable_ret = i2s_channel_enable(tx_handle_);
|
||||
if (enable_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enable I2S TX channel: %s", esp_err_to_name(enable_ret));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Enabled I2S TX channel");
|
||||
}
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
output_sample_rate_ = sample_rate;
|
||||
ESP_LOGI(TAG, "Successfully changed output sample rate to %d Hz", sample_rate);
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to change sample rate to %d Hz: %s", sample_rate, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,62 @@
|
||||
#ifndef _AUDIO_CODEC_H
|
||||
#define _AUDIO_CODEC_H
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <driver/i2s_std.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#define AUDIO_CODEC_DMA_DESC_NUM 6
|
||||
#define AUDIO_CODEC_DMA_FRAME_NUM 240
|
||||
#define AUDIO_CODEC_DEFAULT_MIC_GAIN 30.0
|
||||
|
||||
class AudioCodec {
|
||||
public:
|
||||
AudioCodec();
|
||||
virtual ~AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume);
|
||||
virtual void EnableInput(bool enable);
|
||||
virtual void EnableOutput(bool enable);
|
||||
virtual bool SetOutputSampleRate(int sample_rate);
|
||||
|
||||
virtual void OutputData(std::vector<int16_t>& data);
|
||||
virtual bool InputData(std::vector<int16_t>& data);
|
||||
virtual void Start();
|
||||
|
||||
inline bool duplex() const { return duplex_; }
|
||||
inline bool input_reference() const { return input_reference_; }
|
||||
inline int input_sample_rate() const { return input_sample_rate_; }
|
||||
inline int output_sample_rate() const { return output_sample_rate_; }
|
||||
inline int original_output_sample_rate() const { return original_output_sample_rate_; }
|
||||
inline int input_channels() const { return input_channels_; }
|
||||
inline int output_channels() const { return output_channels_; }
|
||||
inline int output_volume() const { return output_volume_; }
|
||||
inline bool input_enabled() const { return input_enabled_; }
|
||||
inline bool output_enabled() const { return output_enabled_; }
|
||||
|
||||
protected:
|
||||
i2s_chan_handle_t tx_handle_ = nullptr;
|
||||
i2s_chan_handle_t rx_handle_ = nullptr;
|
||||
|
||||
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 original_output_sample_rate_ = 0;
|
||||
int input_channels_ = 1;
|
||||
int output_channels_ = 1;
|
||||
int output_volume_ = 70;
|
||||
|
||||
virtual int Read(int16_t* dest, int samples) = 0;
|
||||
virtual int Write(const int16_t* data, int samples) = 0;
|
||||
};
|
||||
|
||||
#endif // _AUDIO_CODEC_H
|
||||
#ifndef _AUDIO_CODEC_H
|
||||
#define _AUDIO_CODEC_H
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <driver/i2s_std.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#define AUDIO_CODEC_DMA_DESC_NUM 6
|
||||
#define AUDIO_CODEC_DMA_FRAME_NUM 240
|
||||
#define AUDIO_CODEC_DEFAULT_MIC_GAIN 30.0
|
||||
|
||||
class AudioCodec {
|
||||
public:
|
||||
AudioCodec();
|
||||
virtual ~AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume);
|
||||
virtual void EnableInput(bool enable);
|
||||
virtual void EnableOutput(bool enable);
|
||||
virtual bool SetOutputSampleRate(int sample_rate);
|
||||
|
||||
virtual void OutputData(std::vector<int16_t>& data);
|
||||
virtual bool InputData(std::vector<int16_t>& data);
|
||||
virtual void Start();
|
||||
|
||||
inline bool duplex() const { return duplex_; }
|
||||
inline bool input_reference() const { return input_reference_; }
|
||||
inline int input_sample_rate() const { return input_sample_rate_; }
|
||||
inline int output_sample_rate() const { return output_sample_rate_; }
|
||||
inline int original_output_sample_rate() const { return original_output_sample_rate_; }
|
||||
inline int input_channels() const { return input_channels_; }
|
||||
inline int output_channels() const { return output_channels_; }
|
||||
inline int output_volume() const { return output_volume_; }
|
||||
inline bool input_enabled() const { return input_enabled_; }
|
||||
inline bool output_enabled() const { return output_enabled_; }
|
||||
|
||||
protected:
|
||||
i2s_chan_handle_t tx_handle_ = nullptr;
|
||||
i2s_chan_handle_t rx_handle_ = nullptr;
|
||||
|
||||
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 original_output_sample_rate_ = 0;
|
||||
int input_channels_ = 1;
|
||||
int output_channels_ = 1;
|
||||
int output_volume_ = 70;
|
||||
|
||||
virtual int Read(int16_t* dest, int samples) = 0;
|
||||
virtual int Write(const int16_t* data, int samples) = 0;
|
||||
};
|
||||
|
||||
#endif // _AUDIO_CODEC_H
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
#ifndef AUDIO_PROCESSOR_H
|
||||
#define AUDIO_PROCESSOR_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <model_path.h>
|
||||
#include "audio_codec.h"
|
||||
|
||||
class AudioProcessor {
|
||||
public:
|
||||
virtual ~AudioProcessor() = default;
|
||||
|
||||
virtual void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) = 0;
|
||||
virtual void Feed(std::vector<int16_t>&& data) = 0;
|
||||
virtual void Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual bool IsRunning() = 0;
|
||||
virtual void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) = 0;
|
||||
virtual void OnVadStateChange(std::function<void(bool speaking)> callback) = 0;
|
||||
virtual size_t GetFeedSize() = 0;
|
||||
virtual void EnableDeviceAec(bool enable) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifndef AUDIO_PROCESSOR_H
|
||||
#define AUDIO_PROCESSOR_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <model_path.h>
|
||||
#include "audio_codec.h"
|
||||
|
||||
class AudioProcessor {
|
||||
public:
|
||||
virtual ~AudioProcessor() = default;
|
||||
|
||||
virtual void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) = 0;
|
||||
virtual void Feed(std::vector<int16_t>&& data) = 0;
|
||||
virtual void Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual bool IsRunning() = 0;
|
||||
virtual void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) = 0;
|
||||
virtual void OnVadStateChange(std::function<void(bool speaking)> callback) = 0;
|
||||
virtual size_t GetFeedSize() = 0;
|
||||
virtual void EnableDeviceAec(bool enable) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,160 +1,162 @@
|
||||
#ifndef AUDIO_SERVICE_H
|
||||
#define AUDIO_SERVICE_H
|
||||
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <esp_timer.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include <opus_encoder.h>
|
||||
#include <opus_decoder.h>
|
||||
#include <opus_resampler.h>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "audio_processor.h"
|
||||
#include "processors/audio_debugger.h"
|
||||
#include "wake_word.h"
|
||||
#include "protocol.h"
|
||||
|
||||
|
||||
/*
|
||||
* There are two types of audio data flow:
|
||||
* 1. (MIC) -> [Processors] -> {Encode Queue} -> [Opus Encoder] -> {Send Queue} -> (Server)
|
||||
* 2. (Server) -> {Decode Queue} -> [Opus Decoder] -> {Playback Queue} -> (Speaker)
|
||||
*
|
||||
* We use one task for MIC / Speaker / Processors, and one task for Opus Encoder / Opus Decoder.
|
||||
*
|
||||
* Decode Queue and Send Queue are the main queues, because Opus packets are quite smaller than PCM packets.
|
||||
*
|
||||
*/
|
||||
|
||||
#define OPUS_FRAME_DURATION_MS 60
|
||||
#define MAX_ENCODE_TASKS_IN_QUEUE 2
|
||||
#define MAX_PLAYBACK_TASKS_IN_QUEUE 2
|
||||
#define MAX_DECODE_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
|
||||
#define MAX_SEND_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
|
||||
#define AUDIO_TESTING_MAX_DURATION_MS 10000
|
||||
#define MAX_TIMESTAMPS_IN_QUEUE 3
|
||||
|
||||
#define AUDIO_POWER_TIMEOUT_MS 15000
|
||||
#define AUDIO_POWER_CHECK_INTERVAL_MS 1000
|
||||
|
||||
|
||||
#define AS_EVENT_AUDIO_TESTING_RUNNING (1 << 0)
|
||||
#define AS_EVENT_WAKE_WORD_RUNNING (1 << 1)
|
||||
#define AS_EVENT_AUDIO_PROCESSOR_RUNNING (1 << 2)
|
||||
#define AS_EVENT_PLAYBACK_NOT_EMPTY (1 << 3)
|
||||
|
||||
struct AudioServiceCallbacks {
|
||||
std::function<void(void)> on_send_queue_available;
|
||||
std::function<void(const std::string&)> on_wake_word_detected;
|
||||
std::function<void(bool)> on_vad_change;
|
||||
std::function<void(void)> on_audio_testing_queue_full;
|
||||
};
|
||||
|
||||
|
||||
enum AudioTaskType {
|
||||
kAudioTaskTypeEncodeToSendQueue,
|
||||
kAudioTaskTypeEncodeToTestingQueue,
|
||||
kAudioTaskTypeDecodeToPlaybackQueue,
|
||||
};
|
||||
|
||||
struct AudioTask {
|
||||
AudioTaskType type;
|
||||
std::vector<int16_t> pcm;
|
||||
uint32_t timestamp;
|
||||
};
|
||||
|
||||
struct DebugStatistics {
|
||||
uint32_t input_count = 0;
|
||||
uint32_t decode_count = 0;
|
||||
uint32_t encode_count = 0;
|
||||
uint32_t playback_count = 0;
|
||||
};
|
||||
|
||||
class AudioService {
|
||||
public:
|
||||
AudioService();
|
||||
~AudioService();
|
||||
|
||||
void Initialize(AudioCodec* codec);
|
||||
void Start();
|
||||
void Stop();
|
||||
void EncodeWakeWord();
|
||||
std::unique_ptr<AudioStreamPacket> PopWakeWordPacket();
|
||||
const std::string& GetLastWakeWord() const;
|
||||
bool IsVoiceDetected() const { return voice_detected_; }
|
||||
bool IsIdle();
|
||||
bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; }
|
||||
bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; }
|
||||
|
||||
void EnableWakeWordDetection(bool enable);
|
||||
void EnableVoiceProcessing(bool enable);
|
||||
void EnableAudioTesting(bool enable);
|
||||
void EnableDeviceAec(bool enable);
|
||||
|
||||
void SetCallbacks(AudioServiceCallbacks& callbacks);
|
||||
|
||||
bool PushPacketToDecodeQueue(std::unique_ptr<AudioStreamPacket> packet, bool wait = false);
|
||||
std::unique_ptr<AudioStreamPacket> PopPacketFromSendQueue();
|
||||
void PlaySound(const std::string_view& sound);
|
||||
bool ReadAudioData(std::vector<int16_t>& data, int sample_rate, int samples);
|
||||
void ResetDecoder();
|
||||
void SetModelsList(srmodel_list_t* models_list);
|
||||
void UpdateOutputTimestamp();
|
||||
private:
|
||||
AudioCodec* codec_ = nullptr;
|
||||
AudioServiceCallbacks callbacks_;
|
||||
std::unique_ptr<AudioProcessor> audio_processor_;
|
||||
std::unique_ptr<WakeWord> wake_word_;
|
||||
std::unique_ptr<AudioDebugger> audio_debugger_;
|
||||
std::unique_ptr<OpusEncoderWrapper> opus_encoder_;
|
||||
std::unique_ptr<OpusDecoderWrapper> opus_decoder_;
|
||||
OpusResampler input_resampler_;
|
||||
OpusResampler reference_resampler_;
|
||||
OpusResampler output_resampler_;
|
||||
DebugStatistics debug_statistics_;
|
||||
srmodel_list_t* models_list_ = nullptr;
|
||||
|
||||
EventGroupHandle_t event_group_;
|
||||
|
||||
// Audio encode / decode
|
||||
TaskHandle_t audio_input_task_handle_ = nullptr;
|
||||
TaskHandle_t audio_output_task_handle_ = nullptr;
|
||||
TaskHandle_t opus_codec_task_handle_ = nullptr;
|
||||
std::mutex audio_queue_mutex_;
|
||||
std::condition_variable audio_queue_cv_;
|
||||
std::deque<std::unique_ptr<AudioStreamPacket>> audio_decode_queue_;
|
||||
std::deque<std::unique_ptr<AudioStreamPacket>> audio_send_queue_;
|
||||
std::deque<std::unique_ptr<AudioStreamPacket>> audio_testing_queue_;
|
||||
std::deque<std::unique_ptr<AudioTask>> audio_encode_queue_;
|
||||
std::deque<std::unique_ptr<AudioTask>> audio_playback_queue_;
|
||||
// For server AEC
|
||||
std::deque<uint32_t> timestamp_queue_;
|
||||
|
||||
bool wake_word_initialized_ = false;
|
||||
bool audio_processor_initialized_ = false;
|
||||
bool voice_detected_ = false;
|
||||
bool service_stopped_ = true;
|
||||
bool audio_input_need_warmup_ = false;
|
||||
|
||||
esp_timer_handle_t audio_power_timer_ = nullptr;
|
||||
std::chrono::steady_clock::time_point last_input_time_;
|
||||
std::chrono::steady_clock::time_point last_output_time_;
|
||||
|
||||
void AudioInputTask();
|
||||
void AudioOutputTask();
|
||||
void OpusCodecTask();
|
||||
void PushTaskToEncodeQueue(AudioTaskType type, std::vector<int16_t>&& pcm);
|
||||
void SetDecodeSampleRate(int sample_rate, int frame_duration);
|
||||
void CheckAndUpdateAudioPowerState();
|
||||
};
|
||||
|
||||
#ifndef AUDIO_SERVICE_H
|
||||
#define AUDIO_SERVICE_H
|
||||
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <esp_timer.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include <opus_encoder.h>
|
||||
#include <opus_decoder.h>
|
||||
#include <opus_resampler.h>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "audio_processor.h"
|
||||
#include "processors/audio_debugger.h"
|
||||
#include "wake_word.h"
|
||||
#include "protocol.h"
|
||||
|
||||
|
||||
/*
|
||||
* There are two types of audio data flow:
|
||||
* 1. (MIC) -> [Processors] -> {Encode Queue} -> [Opus Encoder] -> {Send Queue} -> (Server)
|
||||
* 2. (Server) -> {Decode Queue} -> [Opus Decoder] -> {Playback Queue} -> (Speaker)
|
||||
*
|
||||
* We use one task for MIC / Speaker / Processors, and one task for Opus Encoder / Opus Decoder.
|
||||
*
|
||||
* Decode Queue and Send Queue are the main queues, because Opus packets are quite smaller than PCM packets.
|
||||
*
|
||||
*/
|
||||
|
||||
#define OPUS_FRAME_DURATION_MS 60
|
||||
#define MAX_ENCODE_TASKS_IN_QUEUE 2
|
||||
#define MAX_PLAYBACK_TASKS_IN_QUEUE 2
|
||||
#define MAX_DECODE_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
|
||||
#define MAX_SEND_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
|
||||
#define AUDIO_TESTING_MAX_DURATION_MS 10000
|
||||
#define MAX_TIMESTAMPS_IN_QUEUE 3
|
||||
|
||||
#define AUDIO_POWER_TIMEOUT_MS 15000
|
||||
#define AUDIO_POWER_CHECK_INTERVAL_MS 1000
|
||||
|
||||
|
||||
#define AS_EVENT_AUDIO_TESTING_RUNNING (1 << 0)
|
||||
#define AS_EVENT_WAKE_WORD_RUNNING (1 << 1)
|
||||
#define AS_EVENT_AUDIO_PROCESSOR_RUNNING (1 << 2)
|
||||
#define AS_EVENT_PLAYBACK_NOT_EMPTY (1 << 3)
|
||||
|
||||
struct AudioServiceCallbacks {
|
||||
std::function<void(void)> on_send_queue_available;
|
||||
std::function<void(const std::string&)> on_wake_word_detected;
|
||||
std::function<void(bool)> on_vad_change;
|
||||
std::function<void(void)> on_audio_testing_queue_full;
|
||||
};
|
||||
|
||||
|
||||
enum AudioTaskType {
|
||||
kAudioTaskTypeEncodeToSendQueue,
|
||||
kAudioTaskTypeEncodeToTestingQueue,
|
||||
kAudioTaskTypeDecodeToPlaybackQueue,
|
||||
};
|
||||
|
||||
struct AudioTask {
|
||||
AudioTaskType type;
|
||||
std::vector<int16_t> pcm;
|
||||
uint32_t timestamp;
|
||||
};
|
||||
|
||||
struct DebugStatistics {
|
||||
uint32_t input_count = 0;
|
||||
uint32_t decode_count = 0;
|
||||
uint32_t encode_count = 0;
|
||||
uint32_t playback_count = 0;
|
||||
};
|
||||
|
||||
class AudioService {
|
||||
public:
|
||||
AudioService();
|
||||
~AudioService();
|
||||
|
||||
void Initialize(AudioCodec* codec);
|
||||
void Start();
|
||||
void Stop();
|
||||
void EncodeWakeWord();
|
||||
std::unique_ptr<AudioStreamPacket> PopWakeWordPacket();
|
||||
const std::string& GetLastWakeWord() const;
|
||||
bool IsVoiceDetected() const { return voice_detected_; }
|
||||
bool IsIdle();
|
||||
bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; }
|
||||
bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; }
|
||||
bool IsAfeWakeWord();
|
||||
|
||||
void EnableWakeWordDetection(bool enable);
|
||||
void EnableVoiceProcessing(bool enable);
|
||||
void EnableAudioTesting(bool enable);
|
||||
void EnableDeviceAec(bool enable);
|
||||
|
||||
void SetCallbacks(AudioServiceCallbacks& callbacks);
|
||||
|
||||
bool PushPacketToDecodeQueue(std::unique_ptr<AudioStreamPacket> packet, bool wait = false);
|
||||
std::unique_ptr<AudioStreamPacket> PopPacketFromSendQueue();
|
||||
void PlaySound(const std::string_view& sound);
|
||||
bool ReadAudioData(std::vector<int16_t>& data, int sample_rate, int samples);
|
||||
void ResetDecoder();
|
||||
void SetModelsList(srmodel_list_t* models_list);
|
||||
void UpdateOutputTimestamp();
|
||||
|
||||
private:
|
||||
AudioCodec* codec_ = nullptr;
|
||||
AudioServiceCallbacks callbacks_;
|
||||
std::unique_ptr<AudioProcessor> audio_processor_;
|
||||
std::unique_ptr<WakeWord> wake_word_;
|
||||
std::unique_ptr<AudioDebugger> audio_debugger_;
|
||||
std::unique_ptr<OpusEncoderWrapper> opus_encoder_;
|
||||
std::unique_ptr<OpusDecoderWrapper> opus_decoder_;
|
||||
OpusResampler input_resampler_;
|
||||
OpusResampler reference_resampler_;
|
||||
OpusResampler output_resampler_;
|
||||
DebugStatistics debug_statistics_;
|
||||
srmodel_list_t* models_list_ = nullptr;
|
||||
|
||||
EventGroupHandle_t event_group_;
|
||||
|
||||
// Audio encode / decode
|
||||
TaskHandle_t audio_input_task_handle_ = nullptr;
|
||||
TaskHandle_t audio_output_task_handle_ = nullptr;
|
||||
TaskHandle_t opus_codec_task_handle_ = nullptr;
|
||||
std::mutex audio_queue_mutex_;
|
||||
std::condition_variable audio_queue_cv_;
|
||||
std::deque<std::unique_ptr<AudioStreamPacket>> audio_decode_queue_;
|
||||
std::deque<std::unique_ptr<AudioStreamPacket>> audio_send_queue_;
|
||||
std::deque<std::unique_ptr<AudioStreamPacket>> audio_testing_queue_;
|
||||
std::deque<std::unique_ptr<AudioTask>> audio_encode_queue_;
|
||||
std::deque<std::unique_ptr<AudioTask>> audio_playback_queue_;
|
||||
// For server AEC
|
||||
std::deque<uint32_t> timestamp_queue_;
|
||||
|
||||
bool wake_word_initialized_ = false;
|
||||
bool audio_processor_initialized_ = false;
|
||||
bool voice_detected_ = false;
|
||||
bool service_stopped_ = true;
|
||||
bool audio_input_need_warmup_ = false;
|
||||
|
||||
esp_timer_handle_t audio_power_timer_ = nullptr;
|
||||
std::chrono::steady_clock::time_point last_input_time_;
|
||||
std::chrono::steady_clock::time_point last_output_time_;
|
||||
|
||||
void AudioInputTask();
|
||||
void AudioOutputTask();
|
||||
void OpusCodecTask();
|
||||
void PushTaskToEncodeQueue(AudioTaskType type, std::vector<int16_t>&& pcm);
|
||||
void SetDecodeSampleRate(int sample_rate, int frame_duration);
|
||||
void CheckAndUpdateAudioPowerState();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,244 +1,244 @@
|
||||
#include "box_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/i2s_tdm.h>
|
||||
|
||||
#define TAG "BoxAudioCodec"
|
||||
|
||||
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_ = input_reference; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = input_reference_ ? 2 : 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = (i2c_port_t)1,
|
||||
.addr = es8311_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(out_ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8311_codec_cfg_t es8311_cfg = {};
|
||||
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 = pa_pin;
|
||||
es8311_cfg.use_mclk = true;
|
||||
es8311_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
out_codec_if_ = es8311_codec_new(&es8311_cfg);
|
||||
assert(out_codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = out_codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
|
||||
// Input
|
||||
i2c_cfg.addr = es7210_addr;
|
||||
in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(in_ctrl_if_ != NULL);
|
||||
|
||||
es7210_codec_cfg_t es7210_cfg = {};
|
||||
es7210_cfg.ctrl_if = in_ctrl_if_;
|
||||
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4;
|
||||
in_codec_if_ = es7210_codec_new(&es7210_cfg);
|
||||
assert(in_codec_if_ != NULL);
|
||||
|
||||
dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
|
||||
dev_cfg.codec_if = in_codec_if_;
|
||||
input_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
|
||||
ESP_LOGI(TAG, "BoxAudioDevice initialized");
|
||||
}
|
||||
|
||||
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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.ext_clk_freq_hz = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
i2s_tdm_config_t tdm_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)input_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.ext_clk_freq_hz = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.bclk_div = 8,
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3),
|
||||
.ws_width = I2S_TDM_AUTO_WS_WIDTH,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = false,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false,
|
||||
.skip_mask = false,
|
||||
.total_slot = I2S_TDM_AUTO_SLOT_NUM
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = I2S_GPIO_UNUSED,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void BoxAudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void BoxAudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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), AUDIO_CODEC_DEFAULT_MIC_GAIN));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void BoxAudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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;
|
||||
#include "box_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/i2s_tdm.h>
|
||||
|
||||
#define TAG "BoxAudioCodec"
|
||||
|
||||
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_ = input_reference; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = input_reference_ ? 2 : 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = (i2c_port_t)1,
|
||||
.addr = es8311_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(out_ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8311_codec_cfg_t es8311_cfg = {};
|
||||
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 = pa_pin;
|
||||
es8311_cfg.use_mclk = true;
|
||||
es8311_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
out_codec_if_ = es8311_codec_new(&es8311_cfg);
|
||||
assert(out_codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = out_codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
|
||||
// Input
|
||||
i2c_cfg.addr = es7210_addr;
|
||||
in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(in_ctrl_if_ != NULL);
|
||||
|
||||
es7210_codec_cfg_t es7210_cfg = {};
|
||||
es7210_cfg.ctrl_if = in_ctrl_if_;
|
||||
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4;
|
||||
in_codec_if_ = es7210_codec_new(&es7210_cfg);
|
||||
assert(in_codec_if_ != NULL);
|
||||
|
||||
dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
|
||||
dev_cfg.codec_if = in_codec_if_;
|
||||
input_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
|
||||
ESP_LOGI(TAG, "BoxAudioDevice initialized");
|
||||
}
|
||||
|
||||
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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.ext_clk_freq_hz = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
i2s_tdm_config_t tdm_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)input_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.ext_clk_freq_hz = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.bclk_div = 8,
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3),
|
||||
.ws_width = I2S_TDM_AUTO_WS_WIDTH,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = false,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false,
|
||||
.skip_mask = false,
|
||||
.total_slot = I2S_TDM_AUTO_SLOT_NUM
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = I2S_GPIO_UNUSED,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void BoxAudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void BoxAudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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), AUDIO_CODEC_DEFAULT_MIC_GAIN));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void BoxAudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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;
|
||||
}
|
||||
@@ -1,40 +1,40 @@
|
||||
#ifndef _BOX_AUDIO_CODEC_H
|
||||
#define _BOX_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class BoxAudioCodec : public AudioCodec {
|
||||
private:
|
||||
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;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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
|
||||
#ifndef _BOX_AUDIO_CODEC_H
|
||||
#define _BOX_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class BoxAudioCodec : public AudioCodec {
|
||||
private:
|
||||
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;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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,20 +1,20 @@
|
||||
#include "dummy_audio_codec.h"
|
||||
|
||||
DummyAudioCodec::DummyAudioCodec(int input_sample_rate, int output_sample_rate) {
|
||||
duplex_ = true;
|
||||
input_reference_ = false;
|
||||
input_channels_ = 1;
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
}
|
||||
|
||||
DummyAudioCodec::~DummyAudioCodec() {
|
||||
}
|
||||
|
||||
int DummyAudioCodec::Read(int16_t* dest, int samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DummyAudioCodec::Write(const int16_t* data, int samples) {
|
||||
return 0;
|
||||
}
|
||||
#include "dummy_audio_codec.h"
|
||||
|
||||
DummyAudioCodec::DummyAudioCodec(int input_sample_rate, int output_sample_rate) {
|
||||
duplex_ = true;
|
||||
input_reference_ = false;
|
||||
input_channels_ = 1;
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
}
|
||||
|
||||
DummyAudioCodec::~DummyAudioCodec() {
|
||||
}
|
||||
|
||||
int DummyAudioCodec::Read(int16_t* dest, int samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DummyAudioCodec::Write(const int16_t* data, int samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
#ifndef _DUMMY_AUDIO_CODEC_H
|
||||
#define _DUMMY_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
class DummyAudioCodec : public AudioCodec {
|
||||
private:
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
|
||||
public:
|
||||
DummyAudioCodec(int input_sample_rate, int output_sample_rate);
|
||||
virtual ~DummyAudioCodec();
|
||||
};
|
||||
|
||||
#ifndef _DUMMY_AUDIO_CODEC_H
|
||||
#define _DUMMY_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
class DummyAudioCodec : public AudioCodec {
|
||||
private:
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
|
||||
public:
|
||||
DummyAudioCodec(int input_sample_rate, int output_sample_rate);
|
||||
virtual ~DummyAudioCodec();
|
||||
};
|
||||
|
||||
#endif // _DUMMY_AUDIO_CODEC_H
|
||||
@@ -1,187 +1,187 @@
|
||||
#include "es8311_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Es8311AudioCodec"
|
||||
|
||||
Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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, bool use_mclk, bool pa_inverted) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = false; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
pa_inverted_ = pa_inverted;
|
||||
|
||||
assert(input_sample_rate_ == output_sample_rate_);
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8311_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8311_codec_cfg_t es8311_cfg = {};
|
||||
es8311_cfg.ctrl_if = ctrl_if_;
|
||||
es8311_cfg.gpio_if = gpio_if_;
|
||||
es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8311_cfg.pa_pin = pa_pin;
|
||||
es8311_cfg.use_mclk = use_mclk;
|
||||
es8311_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
es8311_cfg.pa_reverted = pa_inverted_;
|
||||
codec_if_ = es8311_codec_new(&es8311_cfg);
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
ESP_LOGI(TAG, "Es8311AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8311AudioCodec::~Es8311AudioCodec() {
|
||||
esp_codec_dev_delete(dev_);
|
||||
|
||||
audio_codec_delete_codec_if(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::UpdateDeviceState() {
|
||||
if ((input_enabled_ || output_enabled_) && dev_ == nullptr) {
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(dev_ != NULL);
|
||||
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = 0,
|
||||
.sample_rate = (uint32_t)input_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(dev_, &fs));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, output_volume_));
|
||||
} else if (!input_enabled_ && !output_enabled_ && dev_ != nullptr) {
|
||||
esp_codec_dev_close(dev_);
|
||||
dev_ = nullptr;
|
||||
}
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
int level = output_enabled_ ? 1 : 0;
|
||||
gpio_set_level(pa_pin_, pa_inverted_ ? !level : level);
|
||||
}
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
UpdateDeviceState();
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == output_enabled_) {
|
||||
return;
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
UpdateDeviceState();
|
||||
}
|
||||
|
||||
int Es8311AudioCodec::Read(int16_t* dest, int samples) {
|
||||
if (input_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(dev_, (void*)dest, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
int Es8311AudioCodec::Write(const int16_t* data, int samples) {
|
||||
if (output_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(dev_, (void*)data, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
#include "es8311_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Es8311AudioCodec"
|
||||
|
||||
Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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, bool use_mclk, bool pa_inverted) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = false; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
pa_inverted_ = pa_inverted;
|
||||
|
||||
assert(input_sample_rate_ == output_sample_rate_);
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8311_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8311_codec_cfg_t es8311_cfg = {};
|
||||
es8311_cfg.ctrl_if = ctrl_if_;
|
||||
es8311_cfg.gpio_if = gpio_if_;
|
||||
es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8311_cfg.pa_pin = pa_pin;
|
||||
es8311_cfg.use_mclk = use_mclk;
|
||||
es8311_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
es8311_cfg.pa_reverted = pa_inverted_;
|
||||
codec_if_ = es8311_codec_new(&es8311_cfg);
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
ESP_LOGI(TAG, "Es8311AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8311AudioCodec::~Es8311AudioCodec() {
|
||||
esp_codec_dev_delete(dev_);
|
||||
|
||||
audio_codec_delete_codec_if(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::UpdateDeviceState() {
|
||||
if ((input_enabled_ || output_enabled_) && dev_ == nullptr) {
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(dev_ != NULL);
|
||||
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = 0,
|
||||
.sample_rate = (uint32_t)input_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(dev_, &fs));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, output_volume_));
|
||||
} else if (!input_enabled_ && !output_enabled_ && dev_ != nullptr) {
|
||||
esp_codec_dev_close(dev_);
|
||||
dev_ = nullptr;
|
||||
}
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
int level = output_enabled_ ? 1 : 0;
|
||||
gpio_set_level(pa_pin_, pa_inverted_ ? !level : level);
|
||||
}
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
UpdateDeviceState();
|
||||
}
|
||||
|
||||
void Es8311AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == output_enabled_) {
|
||||
return;
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
UpdateDeviceState();
|
||||
}
|
||||
|
||||
int Es8311AudioCodec::Read(int16_t* dest, int samples) {
|
||||
if (input_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(dev_, (void*)dest, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
int Es8311AudioCodec::Write(const int16_t* data, int samples) {
|
||||
if (output_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(dev_, (void*)data, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
@@ -1,42 +1,42 @@
|
||||
#ifndef _ES8311_AUDIO_CODEC_H
|
||||
#define _ES8311_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class Es8311AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* codec_if_ = nullptr;
|
||||
const audio_codec_gpio_if_t* gpio_if_ = nullptr;
|
||||
|
||||
esp_codec_dev_handle_t dev_ = nullptr;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
bool pa_inverted_ = false;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
|
||||
void UpdateDeviceState();
|
||||
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
|
||||
public:
|
||||
Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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, bool use_mclk = true, bool pa_inverted = false);
|
||||
virtual ~Es8311AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#ifndef _ES8311_AUDIO_CODEC_H
|
||||
#define _ES8311_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class Es8311AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* codec_if_ = nullptr;
|
||||
const audio_codec_gpio_if_t* gpio_if_ = nullptr;
|
||||
|
||||
esp_codec_dev_handle_t dev_ = nullptr;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
bool pa_inverted_ = false;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
|
||||
void UpdateDeviceState();
|
||||
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
|
||||
public:
|
||||
Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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, bool use_mclk = true, bool pa_inverted = false);
|
||||
virtual ~Es8311AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#endif // _ES8311_AUDIO_CODEC_H
|
||||
@@ -1,196 +1,196 @@
|
||||
#include "es8374_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Es8374AudioCodec"
|
||||
|
||||
Es8374AudioCodec::Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8374_addr, bool use_mclk) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = false; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8374_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8374_codec_cfg_t es8374_cfg = {};
|
||||
es8374_cfg.ctrl_if = ctrl_if_;
|
||||
es8374_cfg.gpio_if = gpio_if_;
|
||||
es8374_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8374_cfg.pa_pin = pa_pin;
|
||||
codec_if_ = es8374_codec_new(&es8374_cfg);
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
|
||||
input_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
esp_codec_set_disable_when_closed(output_dev_, false);
|
||||
esp_codec_set_disable_when_closed(input_dev_, false);
|
||||
ESP_LOGI(TAG, "Es8374AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8374AudioCodec::~Es8374AudioCodec() {
|
||||
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(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = 6,
|
||||
.dma_frame_num = 240,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = 0,
|
||||
.sample_rate = (uint32_t)input_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 1);
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 0);
|
||||
}
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int Es8374AudioCodec::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 Es8374AudioCodec::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;
|
||||
#include "es8374_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Es8374AudioCodec"
|
||||
|
||||
Es8374AudioCodec::Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8374_addr, bool use_mclk) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = false; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8374_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8374_codec_cfg_t es8374_cfg = {};
|
||||
es8374_cfg.ctrl_if = ctrl_if_;
|
||||
es8374_cfg.gpio_if = gpio_if_;
|
||||
es8374_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8374_cfg.pa_pin = pa_pin;
|
||||
codec_if_ = es8374_codec_new(&es8374_cfg);
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
|
||||
input_dev_ = esp_codec_dev_new(&dev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
esp_codec_set_disable_when_closed(output_dev_, false);
|
||||
esp_codec_set_disable_when_closed(input_dev_, false);
|
||||
ESP_LOGI(TAG, "Es8374AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8374AudioCodec::~Es8374AudioCodec() {
|
||||
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(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = 6,
|
||||
.dma_frame_num = 240,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = 0,
|
||||
.sample_rate = (uint32_t)input_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, AUDIO_CODEC_DEFAULT_MIC_GAIN));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void Es8374AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 1);
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 0);
|
||||
}
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int Es8374AudioCodec::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 Es8374AudioCodec::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;
|
||||
}
|
||||
@@ -1,41 +1,41 @@
|
||||
#ifndef _ES8374_AUDIO_CODEC_H
|
||||
#define _ES8374_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class Es8374AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* 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;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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:
|
||||
Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8374_addr, bool use_mclk = true);
|
||||
virtual ~Es8374AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#ifndef _ES8374_AUDIO_CODEC_H
|
||||
#define _ES8374_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class Es8374AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* 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;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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:
|
||||
Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8374_addr, bool use_mclk = true);
|
||||
virtual ~Es8374AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#endif // _ES8374_AUDIO_CODEC_H
|
||||
@@ -1,219 +1,219 @@
|
||||
#include "es8388_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Es8388AudioCodec"
|
||||
|
||||
Es8388AudioCodec::Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8388_addr, bool input_reference) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = input_reference; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = input_reference_ ? 2 : 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8388_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8388_codec_cfg_t es8388_cfg = {};
|
||||
es8388_cfg.ctrl_if = ctrl_if_;
|
||||
es8388_cfg.gpio_if = gpio_if_;
|
||||
es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8388_cfg.master_mode = true;
|
||||
es8388_cfg.pa_pin = pa_pin;
|
||||
es8388_cfg.pa_reverted = false;
|
||||
es8388_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8388_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
codec_if_ = es8388_codec_new(&es8388_cfg);
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t outdev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&outdev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t indev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
input_dev_ = esp_codec_dev_new(&indev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
esp_codec_set_disable_when_closed(output_dev_, false);
|
||||
esp_codec_set_disable_when_closed(input_dev_, false);
|
||||
ESP_LOGI(TAG, "Es8388AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8388AudioCodec::~Es8388AudioCodec() {
|
||||
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(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.ext_clk_freq_hz = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = (uint8_t) input_channels_,
|
||||
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
|
||||
.sample_rate = (uint32_t)input_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));
|
||||
if (input_reference_) {
|
||||
uint8_t gain = (11 << 4) + 0;
|
||||
ctrl_if_->write_reg(ctrl_if_, 0x09, 1, &gain, 1);
|
||||
}else{
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 24.0));
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == output_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
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_));
|
||||
|
||||
// Set analog output volume to 0dB, default is -45dB
|
||||
uint8_t reg_val = 30; // 0dB
|
||||
if(input_reference_){
|
||||
reg_val = 27;
|
||||
}
|
||||
uint8_t regs[] = { 46, 47, 48, 49 }; // HP_LVOL, HP_RVOL, SPK_LVOL, SPK_RVOL
|
||||
for (uint8_t reg : regs) {
|
||||
ctrl_if_->write_reg(ctrl_if_, reg, 1, ®_val, 1);
|
||||
}
|
||||
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 1);
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 0);
|
||||
}
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int Es8388AudioCodec::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 Es8388AudioCodec::Write(const int16_t* data, int samples) {
|
||||
if (output_enabled_ && output_dev_ && data != nullptr) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
#include "es8388_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Es8388AudioCodec"
|
||||
|
||||
Es8388AudioCodec::Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8388_addr, bool input_reference) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = input_reference; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = input_reference_ ? 2 : 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8388_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8388_codec_cfg_t es8388_cfg = {};
|
||||
es8388_cfg.ctrl_if = ctrl_if_;
|
||||
es8388_cfg.gpio_if = gpio_if_;
|
||||
es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8388_cfg.master_mode = true;
|
||||
es8388_cfg.pa_pin = pa_pin;
|
||||
es8388_cfg.pa_reverted = false;
|
||||
es8388_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8388_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
codec_if_ = es8388_codec_new(&es8388_cfg);
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t outdev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&outdev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t indev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
input_dev_ = esp_codec_dev_new(&indev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
esp_codec_set_disable_when_closed(output_dev_, false);
|
||||
esp_codec_set_disable_when_closed(input_dev_, false);
|
||||
ESP_LOGI(TAG, "Es8388AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8388AudioCodec::~Es8388AudioCodec() {
|
||||
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(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.ext_clk_freq_hz = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = (uint8_t) input_channels_,
|
||||
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
|
||||
.sample_rate = (uint32_t)input_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));
|
||||
if (input_reference_) {
|
||||
uint8_t gain = (11 << 4) + 0;
|
||||
ctrl_if_->write_reg(ctrl_if_, 0x09, 1, &gain, 1);
|
||||
}else{
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 24.0));
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void Es8388AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == output_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
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_));
|
||||
|
||||
// Set analog output volume to 0dB, default is -45dB
|
||||
uint8_t reg_val = 30; // 0dB
|
||||
if(input_reference_){
|
||||
reg_val = 27;
|
||||
}
|
||||
uint8_t regs[] = { 46, 47, 48, 49 }; // HP_LVOL, HP_RVOL, SPK_LVOL, SPK_RVOL
|
||||
for (uint8_t reg : regs) {
|
||||
ctrl_if_->write_reg(ctrl_if_, reg, 1, ®_val, 1);
|
||||
}
|
||||
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 1);
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 0);
|
||||
}
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int Es8388AudioCodec::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 Es8388AudioCodec::Write(const int16_t* data, int samples) {
|
||||
if (output_enabled_ && output_dev_ && data != nullptr) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
#ifndef _ES8388_AUDIO_CODEC_H
|
||||
#define _ES8388_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class Es8388AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* 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;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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:
|
||||
Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8388_addr, bool input_reference = false);
|
||||
virtual ~Es8388AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#endif // _ES8388_AUDIO_CODEC_H
|
||||
#ifndef _ES8388_AUDIO_CODEC_H
|
||||
#define _ES8388_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
class Es8388AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* 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;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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:
|
||||
Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8388_addr, bool input_reference = false);
|
||||
virtual ~Es8388AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#endif // _ES8388_AUDIO_CODEC_H
|
||||
|
||||
@@ -1,203 +1,203 @@
|
||||
#include "es8389_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char TAG[] = "Es8389AudioCodec";
|
||||
|
||||
Es8389AudioCodec::Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8389_addr, bool use_mclk) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = false; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8389_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8389_codec_cfg_t es8389_cfg = {};
|
||||
es8389_cfg.ctrl_if = ctrl_if_;
|
||||
es8389_cfg.gpio_if = gpio_if_;
|
||||
es8389_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8389_cfg.pa_pin = pa_pin;
|
||||
es8389_cfg.use_mclk = use_mclk;
|
||||
es8389_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8389_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
codec_if_ = es8389_codec_new(&es8389_cfg);
|
||||
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t outdev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&outdev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t indev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
input_dev_ = esp_codec_dev_new(&indev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
esp_codec_set_disable_when_closed(output_dev_, false);
|
||||
esp_codec_set_disable_when_closed(input_dev_, false);
|
||||
ESP_LOGI(TAG, "Es8389AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8389AudioCodec::~Es8389AudioCodec() {
|
||||
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(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = 6,
|
||||
.dma_frame_num = 240,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = 0,
|
||||
.sample_rate = (uint32_t)input_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 1);
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 0);
|
||||
}
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int Es8389AudioCodec::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 Es8389AudioCodec::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;
|
||||
#include "es8389_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char TAG[] = "Es8389AudioCodec";
|
||||
|
||||
Es8389AudioCodec::Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8389_addr, bool use_mclk) {
|
||||
duplex_ = true; // 是否双工
|
||||
input_reference_ = false; // 是否使用参考输入,实现回声消除
|
||||
input_channels_ = 1; // 输入通道数
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
pa_pin_ = pa_pin;
|
||||
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 = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = rx_handle_,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
assert(data_if_ != NULL);
|
||||
|
||||
// Output
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = i2c_port,
|
||||
.addr = es8389_addr,
|
||||
.bus_handle = i2c_master_handle,
|
||||
};
|
||||
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
assert(ctrl_if_ != NULL);
|
||||
|
||||
gpio_if_ = audio_codec_new_gpio();
|
||||
assert(gpio_if_ != NULL);
|
||||
|
||||
es8389_codec_cfg_t es8389_cfg = {};
|
||||
es8389_cfg.ctrl_if = ctrl_if_;
|
||||
es8389_cfg.gpio_if = gpio_if_;
|
||||
es8389_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
|
||||
es8389_cfg.pa_pin = pa_pin;
|
||||
es8389_cfg.use_mclk = use_mclk;
|
||||
es8389_cfg.hw_gain.pa_voltage = 5.0;
|
||||
es8389_cfg.hw_gain.codec_dac_voltage = 3.3;
|
||||
codec_if_ = es8389_codec_new(&es8389_cfg);
|
||||
|
||||
assert(codec_if_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t outdev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
output_dev_ = esp_codec_dev_new(&outdev_cfg);
|
||||
assert(output_dev_ != NULL);
|
||||
|
||||
esp_codec_dev_cfg_t indev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN,
|
||||
.codec_if = codec_if_,
|
||||
.data_if = data_if_,
|
||||
};
|
||||
input_dev_ = esp_codec_dev_new(&indev_cfg);
|
||||
assert(input_dev_ != NULL);
|
||||
esp_codec_set_disable_when_closed(output_dev_, false);
|
||||
esp_codec_set_disable_when_closed(input_dev_, false);
|
||||
ESP_LOGI(TAG, "Es8389AudioCodec initialized");
|
||||
}
|
||||
|
||||
Es8389AudioCodec::~Es8389AudioCodec() {
|
||||
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(codec_if_);
|
||||
audio_codec_delete_ctrl_if(ctrl_if_);
|
||||
audio_codec_delete_gpio_if(gpio_if_);
|
||||
audio_codec_delete_data_if(data_if_);
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::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 = {
|
||||
.id = I2S_NUM_0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = 6,
|
||||
.dma_frame_num = 240,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = mclk,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::EnableInput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = 0,
|
||||
.sample_rate = (uint32_t)input_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void Es8389AudioCodec::EnableOutput(bool enable) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
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_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 1);
|
||||
}
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
if (pa_pin_ != GPIO_NUM_NC) {
|
||||
gpio_set_level(pa_pin_, 0);
|
||||
}
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int Es8389AudioCodec::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 Es8389AudioCodec::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;
|
||||
}
|
||||
@@ -1,40 +1,40 @@
|
||||
#ifndef _ES8389_AUDIO_CODEC_H
|
||||
#define _ES8389_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
class Es8389AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* 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;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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:
|
||||
Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8389_addr, bool use_mclk = true);
|
||||
virtual ~Es8389AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#endif // _ES8389_AUDIO_CODEC_H
|
||||
#ifndef _ES8389_AUDIO_CODEC_H
|
||||
#define _ES8389_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/i2c.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <mutex>
|
||||
|
||||
class Es8389AudioCodec : public AudioCodec {
|
||||
private:
|
||||
const audio_codec_data_if_t* data_if_ = nullptr;
|
||||
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
|
||||
const audio_codec_if_t* 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;
|
||||
gpio_num_t pa_pin_ = GPIO_NUM_NC;
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
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:
|
||||
Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, 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 es8389_addr, bool use_mclk = true);
|
||||
virtual ~Es8389AudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
};
|
||||
|
||||
#endif // _ES8389_AUDIO_CODEC_H
|
||||
|
||||
@@ -1,418 +1,410 @@
|
||||
#include "no_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "NoAudioCodec"
|
||||
|
||||
NoAudioCodec::~NoAudioCodec() {
|
||||
if (rx_handle_ != nullptr) {
|
||||
ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_));
|
||||
}
|
||||
if (tx_handle_ != nullptr) {
|
||||
ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_));
|
||||
}
|
||||
}
|
||||
|
||||
NoAudioCodecDuplex::NoAudioCodecDuplex(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,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_MONO,
|
||||
.slot_mask = I2S_STD_SLOT_LEFT,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
|
||||
NoAudioCodecSimplex::NoAudioCodecSimplex(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_port_t)0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
|
||||
// Create a new channel for MIC
|
||||
chan_cfg.id = (i2s_port_t)1;
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_));
|
||||
std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_;
|
||||
// RX 使用单声道 LEFT
|
||||
std_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO;
|
||||
std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
|
||||
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 = mic_din;
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
NoAudioCodecSimplex::NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask){
|
||||
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_port_t)0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_MONO,
|
||||
.slot_mask = spk_slot_mask,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
|
||||
// Create a new channel for MIC
|
||||
chan_cfg.id = (i2s_port_t)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.slot_cfg.slot_mask = mic_slot_mask;
|
||||
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 = mic_din;
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask,gpio_num_t mic_sck, 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 tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER);
|
||||
tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM;
|
||||
tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM;
|
||||
tx_chan_cfg.auto_clear_after_cb = true;
|
||||
tx_chan_cfg.auto_clear_before_cb = false;
|
||||
tx_chan_cfg.intr_priority = 0;
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL));
|
||||
|
||||
|
||||
i2s_std_config_t tx_std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_MONO,
|
||||
.slot_mask = spk_slot_mask,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg));
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
// Create a new channel for MIC in PDM mode
|
||||
i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER);
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_));
|
||||
i2s_pdm_rx_config_t pdm_rx_cfg = {
|
||||
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_),
|
||||
/* The data bit-width of PDM mode is fixed to 16 */
|
||||
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.clk = mic_sck,
|
||||
.din = mic_din,
|
||||
|
||||
.invert_flags = {
|
||||
.clk_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg));
|
||||
#else
|
||||
ESP_LOGE(TAG, "PDM is not supported");
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(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_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 tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER);
|
||||
tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM;
|
||||
tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM;
|
||||
tx_chan_cfg.auto_clear_after_cb = true;
|
||||
tx_chan_cfg.auto_clear_before_cb = false;
|
||||
tx_chan_cfg.intr_priority = 0;
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL));
|
||||
|
||||
|
||||
i2s_std_config_t tx_std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg));
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
// Create a new channel for MIC in PDM mode
|
||||
i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER);
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_));
|
||||
i2s_pdm_rx_config_t pdm_rx_cfg = {
|
||||
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_),
|
||||
/* The data bit-width of PDM mode is fixed to 16 */
|
||||
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.clk = mic_sck,
|
||||
.din = mic_din,
|
||||
|
||||
.invert_flags = {
|
||||
.clk_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg));
|
||||
#else
|
||||
ESP_LOGE(TAG, "PDM is not supported");
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
int NoAudioCodec::Write(const int16_t* data, int samples) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
// 立体声交织输出:L,R,L,R ... (每声道32位)
|
||||
std::vector<int32_t> buffer(samples * 2);
|
||||
|
||||
// output_volume_: 0-100
|
||||
// volume_factor_: 0-65536
|
||||
int32_t volume_factor = pow(double(output_volume_) / 100.0, 2) * 65536;
|
||||
for (int i = 0; i < samples; i++) {
|
||||
int64_t temp = int64_t(data[i]) * volume_factor; // 使用 int64_t 进行乘法运算
|
||||
int32_t s32;
|
||||
if (temp > INT32_MAX) {
|
||||
s32 = INT32_MAX;
|
||||
} else if (temp < INT32_MIN) {
|
||||
s32 = INT32_MIN;
|
||||
} else {
|
||||
s32 = static_cast<int32_t>(temp);
|
||||
}
|
||||
// 交织到左右声道
|
||||
buffer[2 * i] = s32; // Left
|
||||
buffer[2 * i + 1] = s32; // Right(复制)
|
||||
}
|
||||
|
||||
size_t bytes_written;
|
||||
ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), (samples * 2) * sizeof(int32_t), &bytes_written, portMAX_DELAY));
|
||||
return bytes_written / sizeof(int32_t) / 2; // 返回每声道样本数
|
||||
}
|
||||
|
||||
int NoAudioCodec::Read(int16_t* dest, int samples) {
|
||||
size_t bytes_read;
|
||||
|
||||
std::vector<int32_t> bit32_buffer(samples);
|
||||
if (i2s_channel_read(rx_handle_, bit32_buffer.data(), 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;
|
||||
dest[i] = (value > INT16_MAX) ? INT16_MAX : (value < -INT16_MAX) ? -INT16_MAX : (int16_t)value;
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
int NoAudioCodecSimplexPdm::Read(int16_t* dest, int samples) {
|
||||
size_t bytes_read;
|
||||
|
||||
// PDM 解调后的数据位宽为 16 位,直接读取到目标缓冲区
|
||||
if (i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Read Failed!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 计算实际读取的样本数
|
||||
return bytes_read / sizeof(int16_t);
|
||||
}
|
||||
#include "no_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "NoAudioCodec"
|
||||
|
||||
NoAudioCodec::~NoAudioCodec() {
|
||||
if (rx_handle_ != nullptr) {
|
||||
ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_));
|
||||
}
|
||||
if (tx_handle_ != nullptr) {
|
||||
ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_));
|
||||
}
|
||||
}
|
||||
|
||||
NoAudioCodecDuplex::NoAudioCodecDuplex(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,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_MONO,
|
||||
.slot_mask = I2S_STD_SLOT_LEFT,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
|
||||
NoAudioCodecSimplex::NoAudioCodecSimplex(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_port_t)0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_MONO,
|
||||
.slot_mask = I2S_STD_SLOT_LEFT,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
|
||||
// Create a new channel for MIC
|
||||
chan_cfg.id = (i2s_port_t)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 = mic_sck;
|
||||
std_cfg.gpio_cfg.ws = mic_ws;
|
||||
std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED;
|
||||
std_cfg.gpio_cfg.din = mic_din;
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
NoAudioCodecSimplex::NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask){
|
||||
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_port_t)0,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_MONO,
|
||||
.slot_mask = spk_slot_mask,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
|
||||
// Create a new channel for MIC
|
||||
chan_cfg.id = (i2s_port_t)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.slot_cfg.slot_mask = mic_slot_mask;
|
||||
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 = mic_din;
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask,gpio_num_t mic_sck, 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 tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER);
|
||||
tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM;
|
||||
tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM;
|
||||
tx_chan_cfg.auto_clear_after_cb = true;
|
||||
tx_chan_cfg.auto_clear_before_cb = false;
|
||||
tx_chan_cfg.intr_priority = 0;
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL));
|
||||
|
||||
|
||||
i2s_std_config_t tx_std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_MONO,
|
||||
.slot_mask = spk_slot_mask,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg));
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
// Create a new channel for MIC in PDM mode
|
||||
i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER);
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_));
|
||||
i2s_pdm_rx_config_t pdm_rx_cfg = {
|
||||
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_),
|
||||
/* The data bit-width of PDM mode is fixed to 16 */
|
||||
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.clk = mic_sck,
|
||||
.din = mic_din,
|
||||
|
||||
.invert_flags = {
|
||||
.clk_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg));
|
||||
#else
|
||||
ESP_LOGE(TAG, "PDM is not supported");
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(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_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 tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER);
|
||||
tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM;
|
||||
tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM;
|
||||
tx_chan_cfg.auto_clear_after_cb = true;
|
||||
tx_chan_cfg.auto_clear_before_cb = false;
|
||||
tx_chan_cfg.intr_priority = 0;
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL));
|
||||
|
||||
|
||||
i2s_std_config_t tx_std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
|
||||
},
|
||||
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = spk_bclk,
|
||||
.ws = spk_ws,
|
||||
.dout = spk_dout,
|
||||
.din = I2S_GPIO_UNUSED,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg));
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
// Create a new channel for MIC in PDM mode
|
||||
i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER);
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_));
|
||||
i2s_pdm_rx_config_t pdm_rx_cfg = {
|
||||
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_),
|
||||
/* The data bit-width of PDM mode is fixed to 16 */
|
||||
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.clk = mic_sck,
|
||||
.din = mic_din,
|
||||
|
||||
.invert_flags = {
|
||||
.clk_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg));
|
||||
#else
|
||||
ESP_LOGE(TAG, "PDM is not supported");
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Simplex channels created");
|
||||
}
|
||||
|
||||
int NoAudioCodec::Write(const int16_t* data, int samples) {
|
||||
std::lock_guard<std::mutex> lock(data_if_mutex_);
|
||||
std::vector<int32_t> buffer(samples);
|
||||
|
||||
// output_volume_: 0-100
|
||||
// volume_factor_: 0-65536
|
||||
int32_t volume_factor = pow(double(output_volume_) / 100.0, 2) * 65536;
|
||||
for (int i = 0; i < samples; i++) {
|
||||
int64_t temp = int64_t(data[i]) * volume_factor; // 使用 int64_t 进行乘法运算
|
||||
if (temp > INT32_MAX) {
|
||||
buffer[i] = INT32_MAX;
|
||||
} else if (temp < INT32_MIN) {
|
||||
buffer[i] = INT32_MIN;
|
||||
} else {
|
||||
buffer[i] = static_cast<int32_t>(temp);
|
||||
}
|
||||
}
|
||||
|
||||
size_t bytes_written;
|
||||
ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), samples * sizeof(int32_t), &bytes_written, portMAX_DELAY));
|
||||
return bytes_written / sizeof(int32_t);
|
||||
}
|
||||
|
||||
int NoAudioCodec::Read(int16_t* dest, int samples) {
|
||||
size_t bytes_read;
|
||||
|
||||
std::vector<int32_t> bit32_buffer(samples);
|
||||
if (i2s_channel_read(rx_handle_, bit32_buffer.data(), 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;
|
||||
dest[i] = (value > INT16_MAX) ? INT16_MAX : (value < -INT16_MAX) ? -INT16_MAX : (int16_t)value;
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
int NoAudioCodecSimplexPdm::Read(int16_t* dest, int samples) {
|
||||
size_t bytes_read;
|
||||
|
||||
// PDM 解调后的数据位宽为 16 位,直接读取到目标缓冲区
|
||||
if (i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Read Failed!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 计算实际读取的样本数
|
||||
return bytes_read / sizeof(int16_t);
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
#ifndef _NO_AUDIO_CODEC_H
|
||||
#define _NO_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2s_pdm.h>
|
||||
#include <mutex>
|
||||
|
||||
class NoAudioCodec : public AudioCodec {
|
||||
protected:
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
|
||||
public:
|
||||
virtual ~NoAudioCodec();
|
||||
};
|
||||
|
||||
class NoAudioCodecDuplex : public NoAudioCodec {
|
||||
public:
|
||||
NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
|
||||
};
|
||||
|
||||
class NoAudioCodecSimplex : public NoAudioCodec {
|
||||
public:
|
||||
NoAudioCodecSimplex(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);
|
||||
NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask);
|
||||
};
|
||||
|
||||
class NoAudioCodecSimplexPdm : public NoAudioCodec {
|
||||
public:
|
||||
NoAudioCodecSimplexPdm(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_din);
|
||||
NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_din);
|
||||
int Read(int16_t* dest, int samples);
|
||||
};
|
||||
|
||||
#endif // _NO_AUDIO_CODEC_H
|
||||
#ifndef _NO_AUDIO_CODEC_H
|
||||
#define _NO_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2s_pdm.h>
|
||||
#include <mutex>
|
||||
|
||||
class NoAudioCodec : public AudioCodec {
|
||||
protected:
|
||||
std::mutex data_if_mutex_;
|
||||
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
|
||||
public:
|
||||
virtual ~NoAudioCodec();
|
||||
};
|
||||
|
||||
class NoAudioCodecDuplex : public NoAudioCodec {
|
||||
public:
|
||||
NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
|
||||
};
|
||||
|
||||
class NoAudioCodecSimplex : public NoAudioCodec {
|
||||
public:
|
||||
NoAudioCodecSimplex(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);
|
||||
NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask);
|
||||
};
|
||||
|
||||
class NoAudioCodecSimplexPdm : public NoAudioCodec {
|
||||
public:
|
||||
NoAudioCodecSimplexPdm(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_din);
|
||||
NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_din);
|
||||
int Read(int16_t* dest, int samples);
|
||||
};
|
||||
|
||||
#endif // _NO_AUDIO_CODEC_H
|
||||
|
||||
@@ -1,189 +1,187 @@
|
||||
#include "afe_audio_processor.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define PROCESSOR_RUNNING 0x01
|
||||
|
||||
#define TAG "AfeAudioProcessor"
|
||||
|
||||
AfeAudioProcessor::AfeAudioProcessor()
|
||||
: afe_data_(nullptr) {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
frame_samples_ = frame_duration_ms * 16000 / 1000;
|
||||
|
||||
// Pre-allocate output buffer capacity
|
||||
output_buffer_.reserve(frame_samples_);
|
||||
|
||||
int ref_num = codec_->input_reference() ? 1 : 0;
|
||||
|
||||
std::string input_format;
|
||||
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
|
||||
input_format.push_back('M');
|
||||
}
|
||||
for (int i = 0; i < ref_num; i++) {
|
||||
input_format.push_back('R');
|
||||
}
|
||||
|
||||
srmodel_list_t *models;
|
||||
if (models_list == nullptr) {
|
||||
models = esp_srmodel_init("model");
|
||||
} else {
|
||||
models = models_list;
|
||||
}
|
||||
|
||||
char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL);
|
||||
char* vad_model_name = esp_srmodel_filter(models, ESP_VADN_PREFIX, NULL);
|
||||
|
||||
afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF);
|
||||
afe_config->aec_mode = AEC_MODE_VOIP_HIGH_PERF;
|
||||
afe_config->vad_mode = VAD_MODE_0;
|
||||
afe_config->vad_min_noise_ms = 100;
|
||||
if (vad_model_name != nullptr) {
|
||||
afe_config->vad_model_name = vad_model_name;
|
||||
}
|
||||
|
||||
if (ns_model_name != nullptr) {
|
||||
afe_config->ns_init = true;
|
||||
afe_config->ns_model_name = ns_model_name;
|
||||
afe_config->afe_ns_mode = AFE_NS_MODE_NET;
|
||||
} else {
|
||||
afe_config->ns_init = false;
|
||||
}
|
||||
|
||||
afe_config->afe_perferred_core = 1;
|
||||
afe_config->afe_perferred_priority = 1;
|
||||
afe_config->agc_init = false;
|
||||
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
|
||||
|
||||
#ifdef CONFIG_USE_DEVICE_AEC
|
||||
afe_config->aec_init = true;
|
||||
afe_config->vad_init = false;
|
||||
#else
|
||||
afe_config->aec_init = false;
|
||||
afe_config->vad_init = true;
|
||||
#endif
|
||||
|
||||
afe_iface_ = esp_afe_handle_from_config(afe_config);
|
||||
afe_data_ = afe_iface_->create_from_config(afe_config);
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto this_ = (AfeAudioProcessor*)arg;
|
||||
this_->AudioProcessorTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_communication", 4096, this, 3, NULL);
|
||||
}
|
||||
|
||||
AfeAudioProcessor::~AfeAudioProcessor() {
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->destroy(afe_data_);
|
||||
}
|
||||
vEventGroupDelete(event_group_);
|
||||
}
|
||||
|
||||
size_t AfeAudioProcessor::GetFeedSize() {
|
||||
if (afe_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return afe_iface_->get_feed_chunksize(afe_data_);
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Feed(std::vector<int16_t>&& data) {
|
||||
if (afe_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
afe_iface_->feed(afe_data_, data.data());
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Start() {
|
||||
xEventGroupSetBits(event_group_, PROCESSOR_RUNNING);
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Stop() {
|
||||
xEventGroupClearBits(event_group_, PROCESSOR_RUNNING);
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->reset_buffer(afe_data_);
|
||||
}
|
||||
}
|
||||
|
||||
bool AfeAudioProcessor::IsRunning() {
|
||||
return xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING;
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) {
|
||||
output_callback_ = callback;
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::OnVadStateChange(std::function<void(bool speaking)> callback) {
|
||||
vad_state_change_callback_ = callback;
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::AudioProcessorTask() {
|
||||
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
|
||||
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
|
||||
ESP_LOGI(TAG, "Audio communication task started, feed size: %d fetch size: %d",
|
||||
feed_size, fetch_size);
|
||||
|
||||
while (true) {
|
||||
xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||
|
||||
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
|
||||
if ((xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (res == nullptr || res->ret_value == ESP_FAIL) {
|
||||
if (res != nullptr) {
|
||||
ESP_LOGI(TAG, "Error code: %d", res->ret_value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// VAD state change
|
||||
if (vad_state_change_callback_) {
|
||||
if (res->vad_state == VAD_SPEECH && !is_speaking_) {
|
||||
is_speaking_ = true;
|
||||
vad_state_change_callback_(true);
|
||||
} else if (res->vad_state == VAD_SILENCE && is_speaking_) {
|
||||
is_speaking_ = false;
|
||||
vad_state_change_callback_(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (output_callback_) {
|
||||
size_t samples = res->data_size / sizeof(int16_t);
|
||||
|
||||
// Add data to buffer
|
||||
output_buffer_.insert(output_buffer_.end(), res->data, res->data + samples);
|
||||
|
||||
// Output complete frames when buffer has enough data
|
||||
while (output_buffer_.size() >= frame_samples_) {
|
||||
if (output_buffer_.size() == frame_samples_) {
|
||||
// If buffer size equals frame size, move the entire buffer
|
||||
output_callback_(std::move(output_buffer_));
|
||||
output_buffer_.clear();
|
||||
output_buffer_.reserve(frame_samples_);
|
||||
} else {
|
||||
// If buffer size exceeds frame size, copy one frame and remove it
|
||||
output_callback_(std::vector<int16_t>(output_buffer_.begin(), output_buffer_.begin() + frame_samples_));
|
||||
output_buffer_.erase(output_buffer_.begin(), output_buffer_.begin() + frame_samples_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::EnableDeviceAec(bool enable) {
|
||||
if (enable) {
|
||||
#if CONFIG_USE_DEVICE_AEC
|
||||
afe_iface_->disable_vad(afe_data_);
|
||||
afe_iface_->enable_aec(afe_data_);
|
||||
#else
|
||||
ESP_LOGE(TAG, "Device AEC is not supported");
|
||||
#endif
|
||||
} else {
|
||||
afe_iface_->disable_aec(afe_data_);
|
||||
afe_iface_->enable_vad(afe_data_);
|
||||
}
|
||||
}
|
||||
#include "afe_audio_processor.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define PROCESSOR_RUNNING 0x01
|
||||
|
||||
#define TAG "AfeAudioProcessor"
|
||||
|
||||
AfeAudioProcessor::AfeAudioProcessor()
|
||||
: afe_data_(nullptr) {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
frame_samples_ = frame_duration_ms * 16000 / 1000;
|
||||
|
||||
// Pre-allocate output buffer capacity
|
||||
output_buffer_.reserve(frame_samples_);
|
||||
|
||||
int ref_num = codec_->input_reference() ? 1 : 0;
|
||||
|
||||
std::string input_format;
|
||||
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
|
||||
input_format.push_back('M');
|
||||
}
|
||||
for (int i = 0; i < ref_num; i++) {
|
||||
input_format.push_back('R');
|
||||
}
|
||||
|
||||
srmodel_list_t *models;
|
||||
if (models_list == nullptr) {
|
||||
models = esp_srmodel_init("model");
|
||||
} else {
|
||||
models = models_list;
|
||||
}
|
||||
|
||||
char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL);
|
||||
char* vad_model_name = esp_srmodel_filter(models, ESP_VADN_PREFIX, NULL);
|
||||
|
||||
afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF);
|
||||
afe_config->aec_mode = AEC_MODE_VOIP_HIGH_PERF;
|
||||
afe_config->vad_mode = VAD_MODE_0;
|
||||
afe_config->vad_min_noise_ms = 100;
|
||||
if (vad_model_name != nullptr) {
|
||||
afe_config->vad_model_name = vad_model_name;
|
||||
}
|
||||
|
||||
if (ns_model_name != nullptr) {
|
||||
afe_config->ns_init = true;
|
||||
afe_config->ns_model_name = ns_model_name;
|
||||
afe_config->afe_ns_mode = AFE_NS_MODE_NET;
|
||||
} else {
|
||||
afe_config->ns_init = false;
|
||||
}
|
||||
|
||||
afe_config->agc_init = false;
|
||||
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
|
||||
|
||||
#ifdef CONFIG_USE_DEVICE_AEC
|
||||
afe_config->aec_init = true;
|
||||
afe_config->vad_init = false;
|
||||
#else
|
||||
afe_config->aec_init = false;
|
||||
afe_config->vad_init = true;
|
||||
#endif
|
||||
|
||||
afe_iface_ = esp_afe_handle_from_config(afe_config);
|
||||
afe_data_ = afe_iface_->create_from_config(afe_config);
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto this_ = (AfeAudioProcessor*)arg;
|
||||
this_->AudioProcessorTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_communication", 4096, this, 3, NULL);
|
||||
}
|
||||
|
||||
AfeAudioProcessor::~AfeAudioProcessor() {
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->destroy(afe_data_);
|
||||
}
|
||||
vEventGroupDelete(event_group_);
|
||||
}
|
||||
|
||||
size_t AfeAudioProcessor::GetFeedSize() {
|
||||
if (afe_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return afe_iface_->get_feed_chunksize(afe_data_);
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Feed(std::vector<int16_t>&& data) {
|
||||
if (afe_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
afe_iface_->feed(afe_data_, data.data());
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Start() {
|
||||
xEventGroupSetBits(event_group_, PROCESSOR_RUNNING);
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Stop() {
|
||||
xEventGroupClearBits(event_group_, PROCESSOR_RUNNING);
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->reset_buffer(afe_data_);
|
||||
}
|
||||
}
|
||||
|
||||
bool AfeAudioProcessor::IsRunning() {
|
||||
return xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING;
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) {
|
||||
output_callback_ = callback;
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::OnVadStateChange(std::function<void(bool speaking)> callback) {
|
||||
vad_state_change_callback_ = callback;
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::AudioProcessorTask() {
|
||||
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
|
||||
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
|
||||
ESP_LOGI(TAG, "Audio communication task started, feed size: %d fetch size: %d",
|
||||
feed_size, fetch_size);
|
||||
|
||||
while (true) {
|
||||
xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||
|
||||
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
|
||||
if ((xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (res == nullptr || res->ret_value == ESP_FAIL) {
|
||||
if (res != nullptr) {
|
||||
ESP_LOGI(TAG, "Error code: %d", res->ret_value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// VAD state change
|
||||
if (vad_state_change_callback_) {
|
||||
if (res->vad_state == VAD_SPEECH && !is_speaking_) {
|
||||
is_speaking_ = true;
|
||||
vad_state_change_callback_(true);
|
||||
} else if (res->vad_state == VAD_SILENCE && is_speaking_) {
|
||||
is_speaking_ = false;
|
||||
vad_state_change_callback_(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (output_callback_) {
|
||||
size_t samples = res->data_size / sizeof(int16_t);
|
||||
|
||||
// Add data to buffer
|
||||
output_buffer_.insert(output_buffer_.end(), res->data, res->data + samples);
|
||||
|
||||
// Output complete frames when buffer has enough data
|
||||
while (output_buffer_.size() >= frame_samples_) {
|
||||
if (output_buffer_.size() == frame_samples_) {
|
||||
// If buffer size equals frame size, move the entire buffer
|
||||
output_callback_(std::move(output_buffer_));
|
||||
output_buffer_.clear();
|
||||
output_buffer_.reserve(frame_samples_);
|
||||
} else {
|
||||
// If buffer size exceeds frame size, copy one frame and remove it
|
||||
output_callback_(std::vector<int16_t>(output_buffer_.begin(), output_buffer_.begin() + frame_samples_));
|
||||
output_buffer_.erase(output_buffer_.begin(), output_buffer_.begin() + frame_samples_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::EnableDeviceAec(bool enable) {
|
||||
if (enable) {
|
||||
#if CONFIG_USE_DEVICE_AEC
|
||||
afe_iface_->disable_vad(afe_data_);
|
||||
afe_iface_->enable_aec(afe_data_);
|
||||
#else
|
||||
ESP_LOGE(TAG, "Device AEC is not supported");
|
||||
#endif
|
||||
} else {
|
||||
afe_iface_->disable_aec(afe_data_);
|
||||
afe_iface_->enable_vad(afe_data_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
#ifndef AFE_AUDIO_PROCESSOR_H
|
||||
#define AFE_AUDIO_PROCESSOR_H
|
||||
|
||||
#include <esp_afe_sr_models.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "audio_processor.h"
|
||||
#include "audio_codec.h"
|
||||
|
||||
class AfeAudioProcessor : public AudioProcessor {
|
||||
public:
|
||||
AfeAudioProcessor();
|
||||
~AfeAudioProcessor();
|
||||
|
||||
void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override;
|
||||
void Feed(std::vector<int16_t>&& data) override;
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
bool IsRunning() override;
|
||||
void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) override;
|
||||
void OnVadStateChange(std::function<void(bool speaking)> callback) override;
|
||||
size_t GetFeedSize() override;
|
||||
void EnableDeviceAec(bool enable) override;
|
||||
|
||||
private:
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
esp_afe_sr_iface_t* afe_iface_ = nullptr;
|
||||
esp_afe_sr_data_t* afe_data_ = nullptr;
|
||||
std::function<void(std::vector<int16_t>&& data)> output_callback_;
|
||||
std::function<void(bool speaking)> vad_state_change_callback_;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
int frame_samples_ = 0;
|
||||
bool is_speaking_ = false;
|
||||
std::vector<int16_t> output_buffer_;
|
||||
|
||||
void AudioProcessorTask();
|
||||
};
|
||||
|
||||
#ifndef AFE_AUDIO_PROCESSOR_H
|
||||
#define AFE_AUDIO_PROCESSOR_H
|
||||
|
||||
#include <esp_afe_sr_models.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "audio_processor.h"
|
||||
#include "audio_codec.h"
|
||||
|
||||
class AfeAudioProcessor : public AudioProcessor {
|
||||
public:
|
||||
AfeAudioProcessor();
|
||||
~AfeAudioProcessor();
|
||||
|
||||
void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override;
|
||||
void Feed(std::vector<int16_t>&& data) override;
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
bool IsRunning() override;
|
||||
void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) override;
|
||||
void OnVadStateChange(std::function<void(bool speaking)> callback) override;
|
||||
size_t GetFeedSize() override;
|
||||
void EnableDeviceAec(bool enable) override;
|
||||
|
||||
private:
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
esp_afe_sr_iface_t* afe_iface_ = nullptr;
|
||||
esp_afe_sr_data_t* afe_data_ = nullptr;
|
||||
std::function<void(std::vector<int16_t>&& data)> output_callback_;
|
||||
std::function<void(bool speaking)> vad_state_change_callback_;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
int frame_samples_ = 0;
|
||||
bool is_speaking_ = false;
|
||||
std::vector<int16_t> output_buffer_;
|
||||
|
||||
void AudioProcessorTask();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,68 +1,68 @@
|
||||
#include "audio_debugger.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
#include <esp_log.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
#define TAG "AudioDebugger"
|
||||
|
||||
|
||||
AudioDebugger::AudioDebugger() {
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
udp_sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_sockfd_ >= 0) {
|
||||
// 解析配置的服务器地址 "IP:PORT"
|
||||
std::string server_addr = CONFIG_AUDIO_DEBUG_UDP_SERVER;
|
||||
size_t colon_pos = server_addr.find(':');
|
||||
|
||||
if (colon_pos != std::string::npos) {
|
||||
std::string ip = server_addr.substr(0, colon_pos);
|
||||
int port = std::stoi(server_addr.substr(colon_pos + 1));
|
||||
|
||||
memset(&udp_server_addr_, 0, sizeof(udp_server_addr_));
|
||||
udp_server_addr_.sin_family = AF_INET;
|
||||
udp_server_addr_.sin_port = htons(port);
|
||||
inet_pton(AF_INET, ip.c_str(), &udp_server_addr_.sin_addr);
|
||||
|
||||
ESP_LOGI(TAG, "Initialized server address: %s", CONFIG_AUDIO_DEBUG_UDP_SERVER);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid server address: %s, should be IP:PORT", CONFIG_AUDIO_DEBUG_UDP_SERVER);
|
||||
close(udp_sockfd_);
|
||||
udp_sockfd_ = -1;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to create UDP socket: %d", errno);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioDebugger::~AudioDebugger() {
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
if (udp_sockfd_ >= 0) {
|
||||
close(udp_sockfd_);
|
||||
ESP_LOGI(TAG, "Closed UDP socket");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioDebugger::Feed(const std::vector<int16_t>& data) {
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
if (udp_sockfd_ >= 0) {
|
||||
ssize_t sent = sendto(udp_sockfd_, data.data(), data.size() * sizeof(int16_t), 0,
|
||||
(struct sockaddr*)&udp_server_addr_, sizeof(udp_server_addr_));
|
||||
if (sent < 0) {
|
||||
ESP_LOGW(TAG, "Failed to send audio data to %s: %d", CONFIG_AUDIO_DEBUG_UDP_SERVER, errno);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Sent %d bytes audio data to %s", sent, CONFIG_AUDIO_DEBUG_UDP_SERVER);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "audio_debugger.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
#include <esp_log.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
#define TAG "AudioDebugger"
|
||||
|
||||
|
||||
AudioDebugger::AudioDebugger() {
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
udp_sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_sockfd_ >= 0) {
|
||||
// 解析配置的服务器地址 "IP:PORT"
|
||||
std::string server_addr = CONFIG_AUDIO_DEBUG_UDP_SERVER;
|
||||
size_t colon_pos = server_addr.find(':');
|
||||
|
||||
if (colon_pos != std::string::npos) {
|
||||
std::string ip = server_addr.substr(0, colon_pos);
|
||||
int port = std::stoi(server_addr.substr(colon_pos + 1));
|
||||
|
||||
memset(&udp_server_addr_, 0, sizeof(udp_server_addr_));
|
||||
udp_server_addr_.sin_family = AF_INET;
|
||||
udp_server_addr_.sin_port = htons(port);
|
||||
inet_pton(AF_INET, ip.c_str(), &udp_server_addr_.sin_addr);
|
||||
|
||||
ESP_LOGI(TAG, "Initialized server address: %s", CONFIG_AUDIO_DEBUG_UDP_SERVER);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid server address: %s, should be IP:PORT", CONFIG_AUDIO_DEBUG_UDP_SERVER);
|
||||
close(udp_sockfd_);
|
||||
udp_sockfd_ = -1;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to create UDP socket: %d", errno);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
AudioDebugger::~AudioDebugger() {
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
if (udp_sockfd_ >= 0) {
|
||||
close(udp_sockfd_);
|
||||
ESP_LOGI(TAG, "Closed UDP socket");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioDebugger::Feed(const std::vector<int16_t>& data) {
|
||||
#if CONFIG_USE_AUDIO_DEBUGGER
|
||||
if (udp_sockfd_ >= 0) {
|
||||
ssize_t sent = sendto(udp_sockfd_, data.data(), data.size() * sizeof(int16_t), 0,
|
||||
(struct sockaddr*)&udp_server_addr_, sizeof(udp_server_addr_));
|
||||
if (sent < 0) {
|
||||
ESP_LOGW(TAG, "Failed to send audio data to %s: %d", CONFIG_AUDIO_DEBUG_UDP_SERVER, errno);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Sent %d bytes audio data to %s", sent, CONFIG_AUDIO_DEBUG_UDP_SERVER);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#ifndef AUDIO_DEBUGGER_H
|
||||
#define AUDIO_DEBUGGER_H
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
class AudioDebugger {
|
||||
public:
|
||||
AudioDebugger();
|
||||
~AudioDebugger();
|
||||
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
|
||||
private:
|
||||
int udp_sockfd_ = -1;
|
||||
struct sockaddr_in udp_server_addr_;
|
||||
};
|
||||
|
||||
#ifndef AUDIO_DEBUGGER_H
|
||||
#define AUDIO_DEBUGGER_H
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
class AudioDebugger {
|
||||
public:
|
||||
AudioDebugger();
|
||||
~AudioDebugger();
|
||||
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
|
||||
private:
|
||||
int udp_sockfd_ = -1;
|
||||
struct sockaddr_in udp_server_addr_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,59 +1,59 @@
|
||||
#include "no_audio_processor.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "NoAudioProcessor"
|
||||
|
||||
void NoAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
frame_samples_ = frame_duration_ms * 16000 / 1000;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::Feed(std::vector<int16_t>&& data) {
|
||||
if (!is_running_ || !output_callback_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (codec_->input_channels() == 2) {
|
||||
// If input channels is 2, we need to fetch the left channel data
|
||||
auto mono_data = std::vector<int16_t>(data.size() / 2);
|
||||
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
|
||||
mono_data[i] = data[j];
|
||||
}
|
||||
output_callback_(std::move(mono_data));
|
||||
} else {
|
||||
output_callback_(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
void NoAudioProcessor::Start() {
|
||||
is_running_ = true;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::Stop() {
|
||||
is_running_ = false;
|
||||
}
|
||||
|
||||
bool NoAudioProcessor::IsRunning() {
|
||||
return is_running_;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) {
|
||||
output_callback_ = callback;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::OnVadStateChange(std::function<void(bool speaking)> callback) {
|
||||
vad_state_change_callback_ = callback;
|
||||
}
|
||||
|
||||
size_t NoAudioProcessor::GetFeedSize() {
|
||||
if (!codec_) {
|
||||
return 0;
|
||||
}
|
||||
return frame_samples_;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::EnableDeviceAec(bool enable) {
|
||||
if (enable) {
|
||||
ESP_LOGE(TAG, "Device AEC is not supported");
|
||||
}
|
||||
}
|
||||
#include "no_audio_processor.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "NoAudioProcessor"
|
||||
|
||||
void NoAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
frame_samples_ = frame_duration_ms * 16000 / 1000;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::Feed(std::vector<int16_t>&& data) {
|
||||
if (!is_running_ || !output_callback_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (codec_->input_channels() == 2) {
|
||||
// If input channels is 2, we need to fetch the left channel data
|
||||
auto mono_data = std::vector<int16_t>(data.size() / 2);
|
||||
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
|
||||
mono_data[i] = data[j];
|
||||
}
|
||||
output_callback_(std::move(mono_data));
|
||||
} else {
|
||||
output_callback_(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
void NoAudioProcessor::Start() {
|
||||
is_running_ = true;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::Stop() {
|
||||
is_running_ = false;
|
||||
}
|
||||
|
||||
bool NoAudioProcessor::IsRunning() {
|
||||
return is_running_;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) {
|
||||
output_callback_ = callback;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::OnVadStateChange(std::function<void(bool speaking)> callback) {
|
||||
vad_state_change_callback_ = callback;
|
||||
}
|
||||
|
||||
size_t NoAudioProcessor::GetFeedSize() {
|
||||
if (!codec_) {
|
||||
return 0;
|
||||
}
|
||||
return frame_samples_;
|
||||
}
|
||||
|
||||
void NoAudioProcessor::EnableDeviceAec(bool enable) {
|
||||
if (enable) {
|
||||
ESP_LOGE(TAG, "Device AEC is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
#ifndef DUMMY_AUDIO_PROCESSOR_H
|
||||
#define DUMMY_AUDIO_PROCESSOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "audio_processor.h"
|
||||
#include "audio_codec.h"
|
||||
|
||||
class NoAudioProcessor : public AudioProcessor {
|
||||
public:
|
||||
NoAudioProcessor() = default;
|
||||
~NoAudioProcessor() = default;
|
||||
|
||||
void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override;
|
||||
void Feed(std::vector<int16_t>&& data) override;
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
bool IsRunning() override;
|
||||
void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) override;
|
||||
void OnVadStateChange(std::function<void(bool speaking)> callback) override;
|
||||
size_t GetFeedSize() override;
|
||||
void EnableDeviceAec(bool enable) override;
|
||||
|
||||
private:
|
||||
AudioCodec* codec_ = nullptr;
|
||||
int frame_samples_ = 0;
|
||||
std::function<void(std::vector<int16_t>&& data)> output_callback_;
|
||||
std::function<void(bool speaking)> vad_state_change_callback_;
|
||||
bool is_running_ = false;
|
||||
};
|
||||
|
||||
#ifndef DUMMY_AUDIO_PROCESSOR_H
|
||||
#define DUMMY_AUDIO_PROCESSOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "audio_processor.h"
|
||||
#include "audio_codec.h"
|
||||
|
||||
class NoAudioProcessor : public AudioProcessor {
|
||||
public:
|
||||
NoAudioProcessor() = default;
|
||||
~NoAudioProcessor() = default;
|
||||
|
||||
void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override;
|
||||
void Feed(std::vector<int16_t>&& data) override;
|
||||
void Start() override;
|
||||
void Stop() override;
|
||||
bool IsRunning() override;
|
||||
void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) override;
|
||||
void OnVadStateChange(std::function<void(bool speaking)> callback) override;
|
||||
size_t GetFeedSize() override;
|
||||
void EnableDeviceAec(bool enable) override;
|
||||
|
||||
private:
|
||||
AudioCodec* codec_ = nullptr;
|
||||
int frame_samples_ = 0;
|
||||
std::function<void(std::vector<int16_t>&& data)> output_callback_;
|
||||
std::function<void(bool speaking)> vad_state_change_callback_;
|
||||
bool is_running_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,26 +1,26 @@
|
||||
#ifndef WAKE_WORD_H
|
||||
#define WAKE_WORD_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <model_path.h>
|
||||
#include "audio_codec.h"
|
||||
|
||||
class WakeWord {
|
||||
public:
|
||||
virtual ~WakeWord() = default;
|
||||
|
||||
virtual bool Initialize(AudioCodec* codec, srmodel_list_t* models_list) = 0;
|
||||
virtual void Feed(const std::vector<int16_t>& data) = 0;
|
||||
virtual void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) = 0;
|
||||
virtual void Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual size_t GetFeedSize() = 0;
|
||||
virtual void EncodeWakeWordData() = 0;
|
||||
virtual bool GetWakeWordOpus(std::vector<uint8_t>& opus) = 0;
|
||||
virtual const std::string& GetLastDetectedWakeWord() const = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifndef WAKE_WORD_H
|
||||
#define WAKE_WORD_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <model_path.h>
|
||||
#include "audio_codec.h"
|
||||
|
||||
class WakeWord {
|
||||
public:
|
||||
virtual ~WakeWord() = default;
|
||||
|
||||
virtual bool Initialize(AudioCodec* codec, srmodel_list_t* models_list) = 0;
|
||||
virtual void Feed(const std::vector<int16_t>& data) = 0;
|
||||
virtual void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) = 0;
|
||||
virtual void Start() = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual size_t GetFeedSize() = 0;
|
||||
virtual void EncodeWakeWordData() = 0;
|
||||
virtual bool GetWakeWordOpus(std::vector<uint8_t>& opus) = 0;
|
||||
virtual const std::string& GetLastDetectedWakeWord() const = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,208 +1,208 @@
|
||||
#include "afe_wake_word.h"
|
||||
#include "audio_service.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <sstream>
|
||||
|
||||
#define DETECTION_RUNNING_EVENT 1
|
||||
|
||||
#define TAG "AfeWakeWord"
|
||||
|
||||
AfeWakeWord::AfeWakeWord()
|
||||
: afe_data_(nullptr),
|
||||
wake_word_pcm_(),
|
||||
wake_word_opus_() {
|
||||
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
AfeWakeWord::~AfeWakeWord() {
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->destroy(afe_data_);
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_stack_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_stack_);
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_buffer_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
if (models_ != nullptr) {
|
||||
esp_srmodel_deinit(models_);
|
||||
}
|
||||
|
||||
vEventGroupDelete(event_group_);
|
||||
}
|
||||
|
||||
bool AfeWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
int ref_num = codec_->input_reference() ? 1 : 0;
|
||||
|
||||
if (models_list == nullptr) {
|
||||
models_ = esp_srmodel_init("model");
|
||||
} else {
|
||||
models_ = models_list;
|
||||
}
|
||||
|
||||
if (models_ == nullptr || models_->num == -1) {
|
||||
ESP_LOGE(TAG, "Failed to initialize wakenet model");
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < models_->num; i++) {
|
||||
ESP_LOGI(TAG, "Model %d: %s", i, models_->model_name[i]);
|
||||
if (strstr(models_->model_name[i], ESP_WN_PREFIX) != NULL) {
|
||||
wakenet_model_ = models_->model_name[i];
|
||||
auto words = esp_srmodel_get_wake_words(models_, wakenet_model_);
|
||||
// split by ";" to get all wake words
|
||||
std::stringstream ss(words);
|
||||
std::string word;
|
||||
while (std::getline(ss, word, ';')) {
|
||||
wake_words_.push_back(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string input_format;
|
||||
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
|
||||
input_format.push_back('M');
|
||||
}
|
||||
for (int i = 0; i < ref_num; i++) {
|
||||
input_format.push_back('R');
|
||||
}
|
||||
afe_config_t* afe_config = afe_config_init(input_format.c_str(), models_, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
|
||||
afe_config->aec_init = codec_->input_reference();
|
||||
afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;
|
||||
afe_config->afe_perferred_core = 1;
|
||||
afe_config->afe_perferred_priority = 1;
|
||||
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
|
||||
|
||||
afe_iface_ = esp_afe_handle_from_config(afe_config);
|
||||
afe_data_ = afe_iface_->create_from_config(afe_config);
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto this_ = (AfeWakeWord*)arg;
|
||||
this_->AudioDetectionTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_detection", 4096, this, 3, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AfeWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
wake_word_detected_callback_ = callback;
|
||||
}
|
||||
|
||||
void AfeWakeWord::Start() {
|
||||
xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT);
|
||||
}
|
||||
|
||||
void AfeWakeWord::Stop() {
|
||||
xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT);
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->reset_buffer(afe_data_);
|
||||
}
|
||||
}
|
||||
|
||||
void AfeWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (afe_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
afe_iface_->feed(afe_data_, data.data());
|
||||
}
|
||||
|
||||
size_t AfeWakeWord::GetFeedSize() {
|
||||
if (afe_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return afe_iface_->get_feed_chunksize(afe_data_);
|
||||
}
|
||||
|
||||
void AfeWakeWord::AudioDetectionTask() {
|
||||
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
|
||||
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
|
||||
ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d",
|
||||
feed_size, fetch_size);
|
||||
|
||||
while (true) {
|
||||
xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||
|
||||
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
|
||||
if (res == nullptr || res->ret_value == ESP_FAIL) {
|
||||
continue;;
|
||||
}
|
||||
|
||||
// Store the wake word data for voice recognition, like who is speaking
|
||||
StoreWakeWordData(res->data, res->data_size / sizeof(int16_t));
|
||||
|
||||
if (res->wakeup_state == WAKENET_DETECTED) {
|
||||
Stop();
|
||||
last_detected_wake_word_ = wake_words_[res->wakenet_model_index - 1];
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AfeWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) {
|
||||
// store audio data to wake_word_pcm_
|
||||
wake_word_pcm_.emplace_back(std::vector<int16_t>(data, data + samples));
|
||||
// keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512)
|
||||
while (wake_word_pcm_.size() > 2000 / 30) {
|
||||
wake_word_pcm_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void AfeWakeWord::EncodeWakeWordData() {
|
||||
const size_t stack_size = 4096 * 7;
|
||||
wake_word_opus_.clear();
|
||||
if (wake_word_encode_task_stack_ == nullptr) {
|
||||
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
|
||||
assert(wake_word_encode_task_stack_ != nullptr);
|
||||
}
|
||||
if (wake_word_encode_task_buffer_ == nullptr) {
|
||||
wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
|
||||
assert(wake_word_encode_task_buffer_ != nullptr);
|
||||
}
|
||||
|
||||
wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
|
||||
auto this_ = (AfeWakeWord*)arg;
|
||||
{
|
||||
auto start_time = esp_timer_get_time();
|
||||
auto encoder = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
|
||||
encoder->SetComplexity(0); // 0 is the fastest
|
||||
|
||||
int packets = 0;
|
||||
for (auto& pcm: this_->wake_word_pcm_) {
|
||||
encoder->Encode(std::move(pcm), [this_](std::vector<uint8_t>&& opus) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(std::move(opus));
|
||||
this_->wake_word_cv_.notify_all();
|
||||
});
|
||||
packets++;
|
||||
}
|
||||
this_->wake_word_pcm_.clear();
|
||||
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
|
||||
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.push_back(std::vector<uint8_t>());
|
||||
this_->wake_word_cv_.notify_all();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
bool AfeWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
|
||||
std::unique_lock<std::mutex> lock(wake_word_mutex_);
|
||||
wake_word_cv_.wait(lock, [this]() {
|
||||
return !wake_word_opus_.empty();
|
||||
});
|
||||
opus.swap(wake_word_opus_.front());
|
||||
wake_word_opus_.pop_front();
|
||||
return !opus.empty();
|
||||
}
|
||||
#include "afe_wake_word.h"
|
||||
#include "audio_service.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <sstream>
|
||||
|
||||
#define DETECTION_RUNNING_EVENT 1
|
||||
|
||||
#define TAG "AfeWakeWord"
|
||||
|
||||
AfeWakeWord::AfeWakeWord()
|
||||
: afe_data_(nullptr),
|
||||
wake_word_pcm_(),
|
||||
wake_word_opus_() {
|
||||
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
AfeWakeWord::~AfeWakeWord() {
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->destroy(afe_data_);
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_stack_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_stack_);
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_buffer_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
if (models_ != nullptr) {
|
||||
esp_srmodel_deinit(models_);
|
||||
}
|
||||
|
||||
vEventGroupDelete(event_group_);
|
||||
}
|
||||
|
||||
bool AfeWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
int ref_num = codec_->input_reference() ? 1 : 0;
|
||||
|
||||
if (models_list == nullptr) {
|
||||
models_ = esp_srmodel_init("model");
|
||||
} else {
|
||||
models_ = models_list;
|
||||
}
|
||||
|
||||
if (models_ == nullptr || models_->num == -1) {
|
||||
ESP_LOGE(TAG, "Failed to initialize wakenet model");
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < models_->num; i++) {
|
||||
ESP_LOGI(TAG, "Model %d: %s", i, models_->model_name[i]);
|
||||
if (strstr(models_->model_name[i], ESP_WN_PREFIX) != NULL) {
|
||||
wakenet_model_ = models_->model_name[i];
|
||||
auto words = esp_srmodel_get_wake_words(models_, wakenet_model_);
|
||||
// split by ";" to get all wake words
|
||||
std::stringstream ss(words);
|
||||
std::string word;
|
||||
while (std::getline(ss, word, ';')) {
|
||||
wake_words_.push_back(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string input_format;
|
||||
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
|
||||
input_format.push_back('M');
|
||||
}
|
||||
for (int i = 0; i < ref_num; i++) {
|
||||
input_format.push_back('R');
|
||||
}
|
||||
afe_config_t* afe_config = afe_config_init(input_format.c_str(), models_, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
|
||||
afe_config->aec_init = codec_->input_reference();
|
||||
afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;
|
||||
afe_config->afe_perferred_core = 1;
|
||||
afe_config->afe_perferred_priority = 1;
|
||||
afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
|
||||
|
||||
afe_iface_ = esp_afe_handle_from_config(afe_config);
|
||||
afe_data_ = afe_iface_->create_from_config(afe_config);
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto this_ = (AfeWakeWord*)arg;
|
||||
this_->AudioDetectionTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_detection", 4096, this, 3, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AfeWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
wake_word_detected_callback_ = callback;
|
||||
}
|
||||
|
||||
void AfeWakeWord::Start() {
|
||||
xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT);
|
||||
}
|
||||
|
||||
void AfeWakeWord::Stop() {
|
||||
xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT);
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->reset_buffer(afe_data_);
|
||||
}
|
||||
}
|
||||
|
||||
void AfeWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (afe_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
afe_iface_->feed(afe_data_, data.data());
|
||||
}
|
||||
|
||||
size_t AfeWakeWord::GetFeedSize() {
|
||||
if (afe_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return afe_iface_->get_feed_chunksize(afe_data_);
|
||||
}
|
||||
|
||||
void AfeWakeWord::AudioDetectionTask() {
|
||||
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
|
||||
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
|
||||
ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d",
|
||||
feed_size, fetch_size);
|
||||
|
||||
while (true) {
|
||||
xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||
|
||||
auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
|
||||
if (res == nullptr || res->ret_value == ESP_FAIL) {
|
||||
continue;;
|
||||
}
|
||||
|
||||
// Store the wake word data for voice recognition, like who is speaking
|
||||
StoreWakeWordData(res->data, res->data_size / sizeof(int16_t));
|
||||
|
||||
if (res->wakeup_state == WAKENET_DETECTED) {
|
||||
Stop();
|
||||
last_detected_wake_word_ = wake_words_[res->wakenet_model_index - 1];
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AfeWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) {
|
||||
// store audio data to wake_word_pcm_
|
||||
wake_word_pcm_.emplace_back(std::vector<int16_t>(data, data + samples));
|
||||
// keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512)
|
||||
while (wake_word_pcm_.size() > 2000 / 30) {
|
||||
wake_word_pcm_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void AfeWakeWord::EncodeWakeWordData() {
|
||||
const size_t stack_size = 4096 * 7;
|
||||
wake_word_opus_.clear();
|
||||
if (wake_word_encode_task_stack_ == nullptr) {
|
||||
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
|
||||
assert(wake_word_encode_task_stack_ != nullptr);
|
||||
}
|
||||
if (wake_word_encode_task_buffer_ == nullptr) {
|
||||
wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
|
||||
assert(wake_word_encode_task_buffer_ != nullptr);
|
||||
}
|
||||
|
||||
wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
|
||||
auto this_ = (AfeWakeWord*)arg;
|
||||
{
|
||||
auto start_time = esp_timer_get_time();
|
||||
auto encoder = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
|
||||
encoder->SetComplexity(0); // 0 is the fastest
|
||||
|
||||
int packets = 0;
|
||||
for (auto& pcm: this_->wake_word_pcm_) {
|
||||
encoder->Encode(std::move(pcm), [this_](std::vector<uint8_t>&& opus) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(std::move(opus));
|
||||
this_->wake_word_cv_.notify_all();
|
||||
});
|
||||
packets++;
|
||||
}
|
||||
this_->wake_word_pcm_.clear();
|
||||
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
|
||||
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.push_back(std::vector<uint8_t>());
|
||||
this_->wake_word_cv_.notify_all();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
bool AfeWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
|
||||
std::unique_lock<std::mutex> lock(wake_word_mutex_);
|
||||
wake_word_cv_.wait(lock, [this]() {
|
||||
return !wake_word_opus_.empty();
|
||||
});
|
||||
opus.swap(wake_word_opus_.front());
|
||||
wake_word_opus_.pop_front();
|
||||
return !opus.empty();
|
||||
}
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
#ifndef AFE_WAKE_WORD_H
|
||||
#define AFE_WAKE_WORD_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 <model_path.h>
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "wake_word.h"
|
||||
|
||||
class AfeWakeWord : public WakeWord {
|
||||
public:
|
||||
AfeWakeWord();
|
||||
~AfeWakeWord();
|
||||
|
||||
bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
|
||||
void Start();
|
||||
void Stop();
|
||||
size_t GetFeedSize();
|
||||
void EncodeWakeWordData();
|
||||
bool GetWakeWordOpus(std::vector<uint8_t>& opus);
|
||||
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
|
||||
|
||||
private:
|
||||
srmodel_list_t *models_ = nullptr;
|
||||
esp_afe_sr_iface_t* afe_iface_ = nullptr;
|
||||
esp_afe_sr_data_t* afe_data_ = nullptr;
|
||||
char* wakenet_model_ = NULL;
|
||||
std::vector<std::string> wake_words_;
|
||||
EventGroupHandle_t event_group_;
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::string last_detected_wake_word_;
|
||||
|
||||
TaskHandle_t wake_word_encode_task_ = nullptr;
|
||||
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
|
||||
StackType_t* wake_word_encode_task_stack_ = nullptr;
|
||||
std::deque<std::vector<int16_t>> wake_word_pcm_;
|
||||
std::deque<std::vector<uint8_t>> wake_word_opus_;
|
||||
std::mutex wake_word_mutex_;
|
||||
std::condition_variable wake_word_cv_;
|
||||
|
||||
void StoreWakeWordData(const int16_t* data, size_t size);
|
||||
void AudioDetectionTask();
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifndef AFE_WAKE_WORD_H
|
||||
#define AFE_WAKE_WORD_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 <model_path.h>
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "wake_word.h"
|
||||
|
||||
class AfeWakeWord : public WakeWord {
|
||||
public:
|
||||
AfeWakeWord();
|
||||
~AfeWakeWord();
|
||||
|
||||
bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
|
||||
void Start();
|
||||
void Stop();
|
||||
size_t GetFeedSize();
|
||||
void EncodeWakeWordData();
|
||||
bool GetWakeWordOpus(std::vector<uint8_t>& opus);
|
||||
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
|
||||
|
||||
private:
|
||||
srmodel_list_t *models_ = nullptr;
|
||||
esp_afe_sr_iface_t* afe_iface_ = nullptr;
|
||||
esp_afe_sr_data_t* afe_data_ = nullptr;
|
||||
char* wakenet_model_ = NULL;
|
||||
std::vector<std::string> wake_words_;
|
||||
EventGroupHandle_t event_group_;
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::string last_detected_wake_word_;
|
||||
|
||||
TaskHandle_t wake_word_encode_task_ = nullptr;
|
||||
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
|
||||
StackType_t* wake_word_encode_task_stack_ = nullptr;
|
||||
std::deque<std::vector<int16_t>> wake_word_pcm_;
|
||||
std::deque<std::vector<uint8_t>> wake_word_opus_;
|
||||
std::mutex wake_word_mutex_;
|
||||
std::condition_variable wake_word_cv_;
|
||||
|
||||
void StoreWakeWordData(const int16_t* data, size_t size);
|
||||
void AudioDetectionTask();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,190 +1,250 @@
|
||||
#include "custom_wake_word.h"
|
||||
#include "audio_service.h"
|
||||
#include "system_info.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include "esp_mn_iface.h"
|
||||
#include "esp_mn_models.h"
|
||||
#include "esp_mn_speech_commands.h"
|
||||
|
||||
|
||||
#define TAG "CustomWakeWord"
|
||||
|
||||
|
||||
CustomWakeWord::CustomWakeWord()
|
||||
: wake_word_pcm_(), wake_word_opus_() {
|
||||
}
|
||||
|
||||
CustomWakeWord::~CustomWakeWord() {
|
||||
if (multinet_model_data_ != nullptr && multinet_ != nullptr) {
|
||||
multinet_->destroy(multinet_model_data_);
|
||||
multinet_model_data_ = nullptr;
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_stack_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_stack_);
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_buffer_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
if (models_ != nullptr) {
|
||||
esp_srmodel_deinit(models_);
|
||||
}
|
||||
}
|
||||
|
||||
bool CustomWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
|
||||
if (models_list == nullptr) {
|
||||
models_ = esp_srmodel_init("model");
|
||||
} else {
|
||||
models_ = models_list;
|
||||
}
|
||||
|
||||
if (models_ == nullptr || models_->num == -1) {
|
||||
ESP_LOGE(TAG, "Failed to initialize wakenet model");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化 multinet (命令词识别)
|
||||
mn_name_ = esp_srmodel_filter(models_, ESP_MN_PREFIX, ESP_MN_CHINESE);
|
||||
if (mn_name_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to initialize multinet, mn_name is nullptr");
|
||||
ESP_LOGI(TAG, "Please refer to https://pcn7cs20v8cr.feishu.cn/wiki/CpQjwQsCJiQSWSkYEvrcxcbVnwh to add custom wake word");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "multinet: %s", mn_name_);
|
||||
multinet_ = esp_mn_handle_from_name(mn_name_);
|
||||
multinet_model_data_ = multinet_->create(mn_name_, 3000); // 3 秒超时
|
||||
multinet_->set_det_threshold(multinet_model_data_, CONFIG_CUSTOM_WAKE_WORD_THRESHOLD / 100.0f);
|
||||
esp_mn_commands_clear();
|
||||
esp_mn_commands_add(1, CONFIG_CUSTOM_WAKE_WORD);
|
||||
esp_mn_commands_update();
|
||||
|
||||
multinet_->print_active_speech_commands(multinet_model_data_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
wake_word_detected_callback_ = callback;
|
||||
}
|
||||
|
||||
void CustomWakeWord::Start() {
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
void CustomWakeWord::Stop() {
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void CustomWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (multinet_model_data_ == nullptr || !running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_mn_state_t mn_state;
|
||||
// If input channels is 2, we need to fetch the left channel data
|
||||
if (codec_->input_channels() == 2) {
|
||||
auto mono_data = std::vector<int16_t>(data.size() / 2);
|
||||
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
|
||||
mono_data[i] = data[j];
|
||||
}
|
||||
|
||||
StoreWakeWordData(mono_data);
|
||||
mn_state = multinet_->detect(multinet_model_data_, const_cast<int16_t*>(mono_data.data()));
|
||||
} else {
|
||||
StoreWakeWordData(data);
|
||||
mn_state = multinet_->detect(multinet_model_data_, const_cast<int16_t*>(data.data()));
|
||||
}
|
||||
|
||||
if (mn_state == ESP_MN_STATE_DETECTING) {
|
||||
return;
|
||||
} else if (mn_state == ESP_MN_STATE_DETECTED) {
|
||||
esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
|
||||
ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
|
||||
mn_result->command_id[0], mn_result->string, mn_result->prob[0]);
|
||||
|
||||
if (mn_result->command_id[0] == 1) {
|
||||
last_detected_wake_word_ = CONFIG_CUSTOM_WAKE_WORD_DISPLAY;
|
||||
}
|
||||
running_ = false;
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
multinet_->clean(multinet_model_data_);
|
||||
} else if (mn_state == ESP_MN_STATE_TIMEOUT) {
|
||||
ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
|
||||
multinet_->clean(multinet_model_data_);
|
||||
}
|
||||
}
|
||||
|
||||
size_t CustomWakeWord::GetFeedSize() {
|
||||
if (multinet_model_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return multinet_->get_samp_chunksize(multinet_model_data_);
|
||||
}
|
||||
|
||||
void CustomWakeWord::StoreWakeWordData(const std::vector<int16_t>& data) {
|
||||
// store audio data to wake_word_pcm_
|
||||
wake_word_pcm_.push_back(data);
|
||||
// keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512)
|
||||
while (wake_word_pcm_.size() > 2000 / 30) {
|
||||
wake_word_pcm_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void CustomWakeWord::EncodeWakeWordData() {
|
||||
const size_t stack_size = 4096 * 7;
|
||||
wake_word_opus_.clear();
|
||||
if (wake_word_encode_task_stack_ == nullptr) {
|
||||
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
|
||||
assert(wake_word_encode_task_stack_ != nullptr);
|
||||
}
|
||||
if (wake_word_encode_task_buffer_ == nullptr) {
|
||||
wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
|
||||
assert(wake_word_encode_task_buffer_ != nullptr);
|
||||
}
|
||||
|
||||
wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
|
||||
auto this_ = (CustomWakeWord*)arg;
|
||||
{
|
||||
auto start_time = esp_timer_get_time();
|
||||
auto encoder = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
|
||||
encoder->SetComplexity(0); // 0 is the fastest
|
||||
|
||||
int packets = 0;
|
||||
for (auto& pcm: this_->wake_word_pcm_) {
|
||||
encoder->Encode(std::move(pcm), [this_](std::vector<uint8_t>&& opus) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(std::move(opus));
|
||||
this_->wake_word_cv_.notify_all();
|
||||
});
|
||||
packets++;
|
||||
}
|
||||
this_->wake_word_pcm_.clear();
|
||||
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
|
||||
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.push_back(std::vector<uint8_t>());
|
||||
this_->wake_word_cv_.notify_all();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
bool CustomWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
|
||||
std::unique_lock<std::mutex> lock(wake_word_mutex_);
|
||||
wake_word_cv_.wait(lock, [this]() {
|
||||
return !wake_word_opus_.empty();
|
||||
});
|
||||
opus.swap(wake_word_opus_.front());
|
||||
wake_word_opus_.pop_front();
|
||||
return !opus.empty();
|
||||
}
|
||||
#include "custom_wake_word.h"
|
||||
#include "audio_service.h"
|
||||
#include "system_info.h"
|
||||
#include "assets.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_mn_iface.h>
|
||||
#include <esp_mn_models.h>
|
||||
#include <esp_mn_speech_commands.h>
|
||||
#include <cJSON.h>
|
||||
|
||||
|
||||
#define TAG "CustomWakeWord"
|
||||
|
||||
|
||||
CustomWakeWord::CustomWakeWord()
|
||||
: wake_word_pcm_(), wake_word_opus_() {
|
||||
}
|
||||
|
||||
CustomWakeWord::~CustomWakeWord() {
|
||||
if (multinet_model_data_ != nullptr && multinet_ != nullptr) {
|
||||
multinet_->destroy(multinet_model_data_);
|
||||
multinet_model_data_ = nullptr;
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_stack_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_stack_);
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_buffer_ != nullptr) {
|
||||
heap_caps_free(wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
if (models_ != nullptr) {
|
||||
esp_srmodel_deinit(models_);
|
||||
}
|
||||
}
|
||||
|
||||
void CustomWakeWord::ParseWakenetModelConfig() {
|
||||
// Read index.json
|
||||
auto& assets = Assets::GetInstance();
|
||||
void* ptr = nullptr;
|
||||
size_t size = 0;
|
||||
if (!assets.GetAssetData("index.json", ptr, size)) {
|
||||
ESP_LOGE(TAG, "Failed to read index.json");
|
||||
return;
|
||||
}
|
||||
cJSON* root = cJSON_ParseWithLength(static_cast<char*>(ptr), size);
|
||||
if (root == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to parse index.json");
|
||||
return;
|
||||
}
|
||||
cJSON* multinet_model = cJSON_GetObjectItem(root, "multinet_model");
|
||||
if (cJSON_IsObject(multinet_model)) {
|
||||
cJSON* language = cJSON_GetObjectItem(multinet_model, "language");
|
||||
cJSON* duration = cJSON_GetObjectItem(multinet_model, "duration");
|
||||
cJSON* threshold = cJSON_GetObjectItem(multinet_model, "threshold");
|
||||
cJSON* commands = cJSON_GetObjectItem(multinet_model, "commands");
|
||||
if (cJSON_IsString(language)) {
|
||||
language_ = language->valuestring;
|
||||
}
|
||||
if (cJSON_IsNumber(duration)) {
|
||||
duration_ = duration->valueint;
|
||||
}
|
||||
if (cJSON_IsNumber(threshold)) {
|
||||
threshold_ = threshold->valuedouble;
|
||||
}
|
||||
if (cJSON_IsArray(commands)) {
|
||||
for (int i = 0; i < cJSON_GetArraySize(commands); i++) {
|
||||
cJSON* command = cJSON_GetArrayItem(commands, i);
|
||||
if (cJSON_IsObject(command)) {
|
||||
cJSON* command_name = cJSON_GetObjectItem(command, "command");
|
||||
cJSON* text = cJSON_GetObjectItem(command, "text");
|
||||
cJSON* action = cJSON_GetObjectItem(command, "action");
|
||||
if (cJSON_IsString(command_name) && cJSON_IsString(text) && cJSON_IsString(action)) {
|
||||
commands_.push_back({command_name->valuestring, text->valuestring, action->valuestring});
|
||||
ESP_LOGI(TAG, "Command: %s, Text: %s, Action: %s", command_name->valuestring, text->valuestring, action->valuestring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
|
||||
bool CustomWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
commands_.clear();
|
||||
|
||||
if (models_list == nullptr) {
|
||||
language_ = "cn";
|
||||
models_ = esp_srmodel_init("model");
|
||||
#ifdef CONFIG_CUSTOM_WAKE_WORD
|
||||
threshold_ = CONFIG_CUSTOM_WAKE_WORD_THRESHOLD / 100.0f;
|
||||
commands_.push_back({CONFIG_CUSTOM_WAKE_WORD, CONFIG_CUSTOM_WAKE_WORD_DISPLAY, "wake"});
|
||||
#endif
|
||||
} else {
|
||||
models_ = models_list;
|
||||
ParseWakenetModelConfig();
|
||||
}
|
||||
|
||||
if (models_ == nullptr || models_->num == -1) {
|
||||
ESP_LOGE(TAG, "Failed to initialize wakenet model");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化 multinet (命令词识别)
|
||||
mn_name_ = esp_srmodel_filter(models_, ESP_MN_PREFIX, language_.c_str());
|
||||
if (mn_name_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to initialize multinet, mn_name is nullptr");
|
||||
ESP_LOGI(TAG, "Please refer to https://pcn7cs20v8cr.feishu.cn/wiki/CpQjwQsCJiQSWSkYEvrcxcbVnwh to add custom wake word");
|
||||
return false;
|
||||
}
|
||||
|
||||
multinet_ = esp_mn_handle_from_name(mn_name_);
|
||||
multinet_model_data_ = multinet_->create(mn_name_, duration_);
|
||||
multinet_->set_det_threshold(multinet_model_data_, threshold_);
|
||||
esp_mn_commands_clear();
|
||||
for (int i = 0; i < commands_.size(); i++) {
|
||||
esp_mn_commands_add(i + 1, commands_[i].command.c_str());
|
||||
}
|
||||
esp_mn_commands_update();
|
||||
|
||||
multinet_->print_active_speech_commands(multinet_model_data_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
wake_word_detected_callback_ = callback;
|
||||
}
|
||||
|
||||
void CustomWakeWord::Start() {
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
void CustomWakeWord::Stop() {
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void CustomWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (multinet_model_data_ == nullptr || !running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_mn_state_t mn_state;
|
||||
// If input channels is 2, we need to fetch the left channel data
|
||||
if (codec_->input_channels() == 2) {
|
||||
auto mono_data = std::vector<int16_t>(data.size() / 2);
|
||||
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
|
||||
mono_data[i] = data[j];
|
||||
}
|
||||
|
||||
StoreWakeWordData(mono_data);
|
||||
mn_state = multinet_->detect(multinet_model_data_, const_cast<int16_t*>(mono_data.data()));
|
||||
} else {
|
||||
StoreWakeWordData(data);
|
||||
mn_state = multinet_->detect(multinet_model_data_, const_cast<int16_t*>(data.data()));
|
||||
}
|
||||
|
||||
if (mn_state == ESP_MN_STATE_DETECTING) {
|
||||
return;
|
||||
} else if (mn_state == ESP_MN_STATE_DETECTED) {
|
||||
esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
|
||||
for (int i = 0; i < mn_result->num && running_; i++) {
|
||||
ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
|
||||
mn_result->command_id[i], mn_result->string, mn_result->prob[i]);
|
||||
auto& command = commands_[mn_result->command_id[i] - 1];
|
||||
if (command.action == "wake") {
|
||||
last_detected_wake_word_ = command.text;
|
||||
running_ = false;
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
}
|
||||
}
|
||||
multinet_->clean(multinet_model_data_);
|
||||
} else if (mn_state == ESP_MN_STATE_TIMEOUT) {
|
||||
ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
|
||||
multinet_->clean(multinet_model_data_);
|
||||
}
|
||||
}
|
||||
|
||||
size_t CustomWakeWord::GetFeedSize() {
|
||||
if (multinet_model_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return multinet_->get_samp_chunksize(multinet_model_data_);
|
||||
}
|
||||
|
||||
void CustomWakeWord::StoreWakeWordData(const std::vector<int16_t>& data) {
|
||||
// store audio data to wake_word_pcm_
|
||||
wake_word_pcm_.push_back(data);
|
||||
// keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512)
|
||||
while (wake_word_pcm_.size() > 2000 / 30) {
|
||||
wake_word_pcm_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void CustomWakeWord::EncodeWakeWordData() {
|
||||
const size_t stack_size = 4096 * 7;
|
||||
wake_word_opus_.clear();
|
||||
if (wake_word_encode_task_stack_ == nullptr) {
|
||||
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
|
||||
assert(wake_word_encode_task_stack_ != nullptr);
|
||||
}
|
||||
if (wake_word_encode_task_buffer_ == nullptr) {
|
||||
wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
|
||||
assert(wake_word_encode_task_buffer_ != nullptr);
|
||||
}
|
||||
|
||||
wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
|
||||
auto this_ = (CustomWakeWord*)arg;
|
||||
{
|
||||
auto start_time = esp_timer_get_time();
|
||||
auto encoder = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
|
||||
encoder->SetComplexity(0); // 0 is the fastest
|
||||
|
||||
int packets = 0;
|
||||
for (auto& pcm: this_->wake_word_pcm_) {
|
||||
encoder->Encode(std::move(pcm), [this_](std::vector<uint8_t>&& opus) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(std::move(opus));
|
||||
this_->wake_word_cv_.notify_all();
|
||||
});
|
||||
packets++;
|
||||
}
|
||||
this_->wake_word_pcm_.clear();
|
||||
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
|
||||
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.push_back(std::vector<uint8_t>());
|
||||
this_->wake_word_cv_.notify_all();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
|
||||
}
|
||||
|
||||
bool CustomWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
|
||||
std::unique_lock<std::mutex> lock(wake_word_mutex_);
|
||||
wake_word_cv_.wait(lock, [this]() {
|
||||
return !wake_word_opus_.empty();
|
||||
});
|
||||
opus.swap(wake_word_opus_.front());
|
||||
wake_word_opus_.pop_front();
|
||||
return !opus.empty();
|
||||
}
|
||||
|
||||
@@ -1,58 +1,69 @@
|
||||
#ifndef CUSTOM_WAKE_WORD_H
|
||||
#define CUSTOM_WAKE_WORD_H
|
||||
|
||||
#include <esp_attr.h>
|
||||
#include <esp_mn_iface.h>
|
||||
#include <esp_mn_models.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "wake_word.h"
|
||||
|
||||
class CustomWakeWord : public WakeWord {
|
||||
public:
|
||||
CustomWakeWord();
|
||||
~CustomWakeWord();
|
||||
|
||||
bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
|
||||
void Start();
|
||||
void Stop();
|
||||
size_t GetFeedSize();
|
||||
void EncodeWakeWordData();
|
||||
bool GetWakeWordOpus(std::vector<uint8_t>& opus);
|
||||
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
|
||||
|
||||
private:
|
||||
// multinet 相关成员变量
|
||||
esp_mn_iface_t* multinet_ = nullptr;
|
||||
model_iface_data_t* multinet_model_data_ = nullptr;
|
||||
srmodel_list_t *models_ = nullptr;
|
||||
char* mn_name_ = nullptr;
|
||||
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::string last_detected_wake_word_;
|
||||
std::atomic<bool> running_ = false;
|
||||
|
||||
TaskHandle_t wake_word_encode_task_ = nullptr;
|
||||
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
|
||||
StackType_t* wake_word_encode_task_stack_ = nullptr;
|
||||
std::deque<std::vector<int16_t>> wake_word_pcm_;
|
||||
std::deque<std::vector<uint8_t>> wake_word_opus_;
|
||||
std::mutex wake_word_mutex_;
|
||||
std::condition_variable wake_word_cv_;
|
||||
|
||||
void StoreWakeWordData(const std::vector<int16_t>& data);
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifndef CUSTOM_WAKE_WORD_H
|
||||
#define CUSTOM_WAKE_WORD_H
|
||||
|
||||
#include <esp_attr.h>
|
||||
#include <esp_mn_iface.h>
|
||||
#include <esp_mn_models.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "wake_word.h"
|
||||
|
||||
class CustomWakeWord : public WakeWord {
|
||||
public:
|
||||
CustomWakeWord();
|
||||
~CustomWakeWord();
|
||||
|
||||
bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
|
||||
void Start();
|
||||
void Stop();
|
||||
size_t GetFeedSize();
|
||||
void EncodeWakeWordData();
|
||||
bool GetWakeWordOpus(std::vector<uint8_t>& opus);
|
||||
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
|
||||
|
||||
private:
|
||||
struct Command {
|
||||
std::string command;
|
||||
std::string text;
|
||||
std::string action;
|
||||
};
|
||||
|
||||
// multinet 相关成员变量
|
||||
esp_mn_iface_t* multinet_ = nullptr;
|
||||
model_iface_data_t* multinet_model_data_ = nullptr;
|
||||
srmodel_list_t *models_ = nullptr;
|
||||
char* mn_name_ = nullptr;
|
||||
std::string language_ = "cn";
|
||||
int duration_ = 3000;
|
||||
float threshold_ = 0.2;
|
||||
std::deque<Command> commands_;
|
||||
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::string last_detected_wake_word_;
|
||||
std::atomic<bool> running_ = false;
|
||||
|
||||
TaskHandle_t wake_word_encode_task_ = nullptr;
|
||||
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
|
||||
StackType_t* wake_word_encode_task_stack_ = nullptr;
|
||||
std::deque<std::vector<int16_t>> wake_word_pcm_;
|
||||
std::deque<std::vector<uint8_t>> wake_word_opus_;
|
||||
std::mutex wake_word_mutex_;
|
||||
std::condition_variable wake_word_cv_;
|
||||
|
||||
void StoreWakeWordData(const std::vector<int16_t>& data);
|
||||
void ParseWakenetModelConfig();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
#include "esp_wake_word.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
|
||||
#define TAG "EspWakeWord"
|
||||
|
||||
EspWakeWord::EspWakeWord() {
|
||||
}
|
||||
|
||||
EspWakeWord::~EspWakeWord() {
|
||||
if (wakenet_data_ != nullptr) {
|
||||
wakenet_iface_->destroy(wakenet_data_);
|
||||
esp_srmodel_deinit(wakenet_model_);
|
||||
}
|
||||
}
|
||||
|
||||
bool EspWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
|
||||
if (models_list == nullptr) {
|
||||
wakenet_model_ = esp_srmodel_init("model");
|
||||
} else {
|
||||
wakenet_model_ = models_list;
|
||||
}
|
||||
|
||||
if (wakenet_model_ == nullptr || wakenet_model_->num == -1) {
|
||||
ESP_LOGE(TAG, "Failed to initialize wakenet model");
|
||||
return false;
|
||||
}
|
||||
if(wakenet_model_->num > 1) {
|
||||
ESP_LOGW(TAG, "More than one model found, using the first one");
|
||||
} else if (wakenet_model_->num == 0) {
|
||||
ESP_LOGE(TAG, "No model found");
|
||||
return false;
|
||||
}
|
||||
char *model_name = wakenet_model_->model_name[0];
|
||||
wakenet_iface_ = (esp_wn_iface_t*)esp_wn_handle_from_name(model_name);
|
||||
wakenet_data_ = wakenet_iface_->create(model_name, DET_MODE_95);
|
||||
|
||||
int frequency = wakenet_iface_->get_samp_rate(wakenet_data_);
|
||||
int audio_chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_);
|
||||
ESP_LOGI(TAG, "Wake word(%s),freq: %d, chunksize: %d", model_name, frequency, audio_chunksize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
wake_word_detected_callback_ = callback;
|
||||
}
|
||||
|
||||
void EspWakeWord::Start() {
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
void EspWakeWord::Stop() {
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void EspWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (wakenet_data_ == nullptr || !running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data());
|
||||
if (res > 0) {
|
||||
last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res);
|
||||
running_ = false;
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t EspWakeWord::GetFeedSize() {
|
||||
if (wakenet_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return wakenet_iface_->get_samp_chunksize(wakenet_data_);
|
||||
}
|
||||
|
||||
void EspWakeWord::EncodeWakeWordData() {
|
||||
}
|
||||
|
||||
bool EspWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
|
||||
return false;
|
||||
}
|
||||
#include "esp_wake_word.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
|
||||
#define TAG "EspWakeWord"
|
||||
|
||||
EspWakeWord::EspWakeWord() {
|
||||
}
|
||||
|
||||
EspWakeWord::~EspWakeWord() {
|
||||
if (wakenet_data_ != nullptr) {
|
||||
wakenet_iface_->destroy(wakenet_data_);
|
||||
esp_srmodel_deinit(wakenet_model_);
|
||||
}
|
||||
}
|
||||
|
||||
bool EspWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
|
||||
codec_ = codec;
|
||||
|
||||
if (models_list == nullptr) {
|
||||
wakenet_model_ = esp_srmodel_init("model");
|
||||
} else {
|
||||
wakenet_model_ = models_list;
|
||||
}
|
||||
|
||||
if (wakenet_model_ == nullptr || wakenet_model_->num == -1) {
|
||||
ESP_LOGE(TAG, "Failed to initialize wakenet model");
|
||||
return false;
|
||||
}
|
||||
if(wakenet_model_->num > 1) {
|
||||
ESP_LOGW(TAG, "More than one model found, using the first one");
|
||||
} else if (wakenet_model_->num == 0) {
|
||||
ESP_LOGE(TAG, "No model found");
|
||||
return false;
|
||||
}
|
||||
char *model_name = wakenet_model_->model_name[0];
|
||||
wakenet_iface_ = (esp_wn_iface_t*)esp_wn_handle_from_name(model_name);
|
||||
wakenet_data_ = wakenet_iface_->create(model_name, DET_MODE_95);
|
||||
|
||||
int frequency = wakenet_iface_->get_samp_rate(wakenet_data_);
|
||||
int audio_chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_);
|
||||
ESP_LOGI(TAG, "Wake word(%s),freq: %d, chunksize: %d", model_name, frequency, audio_chunksize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
wake_word_detected_callback_ = callback;
|
||||
}
|
||||
|
||||
void EspWakeWord::Start() {
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
void EspWakeWord::Stop() {
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void EspWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (wakenet_data_ == nullptr || !running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data());
|
||||
if (res > 0) {
|
||||
last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res);
|
||||
running_ = false;
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t EspWakeWord::GetFeedSize() {
|
||||
if (wakenet_data_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return wakenet_iface_->get_samp_chunksize(wakenet_data_);
|
||||
}
|
||||
|
||||
void EspWakeWord::EncodeWakeWordData() {
|
||||
}
|
||||
|
||||
bool EspWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
#ifndef ESP_WAKE_WORD_H
|
||||
#define ESP_WAKE_WORD_H
|
||||
|
||||
#include <esp_wn_iface.h>
|
||||
#include <esp_wn_models.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "wake_word.h"
|
||||
|
||||
class EspWakeWord : public WakeWord {
|
||||
public:
|
||||
EspWakeWord();
|
||||
~EspWakeWord();
|
||||
|
||||
bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
|
||||
void Start();
|
||||
void Stop();
|
||||
size_t GetFeedSize();
|
||||
void EncodeWakeWordData();
|
||||
bool GetWakeWordOpus(std::vector<uint8_t>& opus);
|
||||
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
|
||||
|
||||
private:
|
||||
esp_wn_iface_t *wakenet_iface_ = nullptr;
|
||||
model_iface_data_t *wakenet_data_ = nullptr;
|
||||
srmodel_list_t *wakenet_model_ = nullptr;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::atomic<bool> running_ = false;
|
||||
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
std::string last_detected_wake_word_;
|
||||
};
|
||||
|
||||
#endif
|
||||
#ifndef ESP_WAKE_WORD_H
|
||||
#define ESP_WAKE_WORD_H
|
||||
|
||||
#include <esp_wn_iface.h>
|
||||
#include <esp_wn_models.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "wake_word.h"
|
||||
|
||||
class EspWakeWord : public WakeWord {
|
||||
public:
|
||||
EspWakeWord();
|
||||
~EspWakeWord();
|
||||
|
||||
bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
|
||||
void Feed(const std::vector<int16_t>& data);
|
||||
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
|
||||
void Start();
|
||||
void Stop();
|
||||
size_t GetFeedSize();
|
||||
void EncodeWakeWordData();
|
||||
bool GetWakeWordOpus(std::vector<uint8_t>& opus);
|
||||
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
|
||||
|
||||
private:
|
||||
esp_wn_iface_t *wakenet_iface_ = nullptr;
|
||||
model_iface_data_t *wakenet_data_ = nullptr;
|
||||
srmodel_list_t *wakenet_model_ = nullptr;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::atomic<bool> running_ = false;
|
||||
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
std::string last_detected_wake_word_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,326 +1,326 @@
|
||||
# 自定义开发板指南
|
||||
|
||||
本指南介绍如何为小智AI语音聊天机器人项目定制一个新的开发板初始化程序。小智AI支持70多种ESP32系列开发板,每个开发板的初始化代码都放在对应的目录下。
|
||||
|
||||
## 重要提示
|
||||
|
||||
> **警告**: 对于自定义开发板,当IO配置与原有开发板不同时,切勿直接覆盖原有开发板的配置编译固件。必须创建新的开发板类型,或者通过config.json文件中的builds配置不同的name和sdkconfig宏定义来区分。使用 `python scripts/release.py [开发板目录名字]` 来编译打包固件。
|
||||
>
|
||||
> 如果直接覆盖原有配置,将来OTA升级时,您的自定义固件可能会被原有开发板的标准固件覆盖,导致您的设备无法正常工作。每个开发板有唯一的标识和对应的固件升级通道,保持开发板标识的唯一性非常重要。
|
||||
|
||||
## 目录结构
|
||||
|
||||
每个开发板的目录结构通常包含以下文件:
|
||||
|
||||
- `xxx_board.cc` - 主要的板级初始化代码,实现了板子相关的初始化和功能
|
||||
- `config.h` - 板级配置文件,定义了硬件管脚映射和其他配置项
|
||||
- `config.json` - 编译配置,指定目标芯片和特殊的编译选项
|
||||
- `README.md` - 开发板相关的说明文档
|
||||
|
||||
## 定制开发板步骤
|
||||
|
||||
### 1. 创建新的开发板目录
|
||||
|
||||
首先在`boards/`目录下创建一个新的目录,例如`my-custom-board/`:
|
||||
|
||||
```bash
|
||||
mkdir main/boards/my-custom-board
|
||||
```
|
||||
|
||||
### 2. 创建配置文件
|
||||
|
||||
#### config.h
|
||||
|
||||
在`config.h`中定义所有的硬件配置,包括:
|
||||
|
||||
- 音频采样率和I2S引脚配置
|
||||
- 音频编解码芯片地址和I2C引脚配置
|
||||
- 按钮和LED引脚配置
|
||||
- 显示屏参数和引脚配置
|
||||
|
||||
参考示例(来自lichuang-c3-dev):
|
||||
|
||||
```c
|
||||
#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_I2S_GPIO_MCLK GPIO_NUM_10
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
|
||||
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_13
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
// 按钮配置
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_9
|
||||
|
||||
// 显示屏配置
|
||||
#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3
|
||||
#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5
|
||||
#define DISPLAY_DC_PIN GPIO_NUM_6
|
||||
#define DISPLAY_SPI_CS_PIN GPIO_NUM_4
|
||||
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
```
|
||||
|
||||
#### config.json
|
||||
|
||||
在`config.json`中定义编译配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"target": "esp32s3", // 目标芯片型号: esp32, esp32s3, esp32c3等
|
||||
"builds": [
|
||||
{
|
||||
"name": "my-custom-board", // 开发板名称
|
||||
"sdkconfig_append": [
|
||||
// 额外需要的编译配置
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 编写板级初始化代码
|
||||
|
||||
创建一个`my_custom_board.cc`文件,实现开发板的所有初始化逻辑。
|
||||
|
||||
一个基本的开发板类定义包含以下几个部分:
|
||||
|
||||
1. **类定义**:继承自`WifiBoard`或`Ml307Board`
|
||||
2. **初始化函数**:包括I2C、显示屏、按钮、IoT等组件的初始化
|
||||
3. **虚函数重写**:如`GetAudioCodec()`、`GetDisplay()`、`GetBacklight()`等
|
||||
4. **注册开发板**:使用`DECLARE_BOARD`宏注册开发板
|
||||
|
||||
```cpp
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "mcp_server.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#define TAG "MyCustomBoard"
|
||||
|
||||
class MyCustomBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
|
||||
// I2C初始化
|
||||
void InitializeI2c() {
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// SPI初始化(用于显示屏)
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
// 按钮初始化
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
// 显示屏初始化(以ST7789为例)
|
||||
void InitializeDisplay() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
|
||||
io_config.dc_gpio_num = DISPLAY_DC_PIN;
|
||||
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(SPI2_HOST, &io_config, &panel_io));
|
||||
|
||||
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);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
// 创建显示屏对象
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT,
|
||||
DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
|
||||
DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
// MCP Tools 初始化
|
||||
void InitializeTools() {
|
||||
// 参考 MCP 文档
|
||||
}
|
||||
|
||||
public:
|
||||
// 构造函数
|
||||
MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeDisplay();
|
||||
InitializeButtons();
|
||||
InitializeTools();
|
||||
GetBacklight()->SetBrightness(100);
|
||||
}
|
||||
|
||||
// 获取音频编解码器
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
codec_i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
// 获取显示屏
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
// 获取背光控制
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
};
|
||||
|
||||
// 注册开发板
|
||||
DECLARE_BOARD(MyCustomBoard);
|
||||
```
|
||||
|
||||
### 4. 创建README.md
|
||||
|
||||
在README.md中说明开发板的特性、硬件要求、编译和烧录步骤:
|
||||
|
||||
|
||||
## 常见开发板组件
|
||||
|
||||
### 1. 显示屏
|
||||
|
||||
项目支持多种显示屏驱动,包括:
|
||||
- ST7789 (SPI)
|
||||
- ILI9341 (SPI)
|
||||
- SH8601 (QSPI)
|
||||
- 等...
|
||||
|
||||
### 2. 音频编解码器
|
||||
|
||||
支持的编解码器包括:
|
||||
- ES8311 (常用)
|
||||
- ES7210 (麦克风阵列)
|
||||
- AW88298 (功放)
|
||||
- 等...
|
||||
|
||||
### 3. 电源管理
|
||||
|
||||
一些开发板使用电源管理芯片:
|
||||
- AXP2101
|
||||
- 其他可用的PMIC
|
||||
|
||||
### 4. MCP设备控制
|
||||
|
||||
可以添加各种MCP工具,让AI能够使用:
|
||||
- Speaker (扬声器控制)
|
||||
- Screen (屏幕亮度调节)
|
||||
- Battery (电池电量读取)
|
||||
- Light (灯光控制)
|
||||
- 等...
|
||||
|
||||
## 开发板类继承关系
|
||||
|
||||
- `Board` - 基础板级类
|
||||
- `WifiBoard` - Wi-Fi连接的开发板
|
||||
- `Ml307Board` - 使用4G模块的开发板
|
||||
- `DualNetworkBoard` - 支持Wi-Fi与4G网络切换的开发板
|
||||
|
||||
## 开发技巧
|
||||
|
||||
1. **参考相似的开发板**:如果您的新开发板与现有开发板有相似之处,可以参考现有实现
|
||||
2. **分步调试**:先实现基础功能(如显示),再添加更复杂的功能(如音频)
|
||||
3. **管脚映射**:确保在config.h中正确配置所有管脚映射
|
||||
4. **检查硬件兼容性**:确认所有芯片和驱动程序的兼容性
|
||||
|
||||
## 可能遇到的问题
|
||||
|
||||
1. **显示屏不正常**:检查SPI配置、镜像设置和颜色反转设置
|
||||
2. **音频无输出**:检查I2S配置、PA使能引脚和编解码器地址
|
||||
3. **无法连接网络**:检查Wi-Fi凭据和网络配置
|
||||
4. **无法与服务器通信**:检查MQTT或WebSocket配置
|
||||
|
||||
## 参考资料
|
||||
|
||||
- ESP-IDF 文档: https://docs.espressif.com/projects/esp-idf/
|
||||
- LVGL 文档: https://docs.lvgl.io/
|
||||
# 自定义开发板指南
|
||||
|
||||
本指南介绍如何为小智AI语音聊天机器人项目定制一个新的开发板初始化程序。小智AI支持70多种ESP32系列开发板,每个开发板的初始化代码都放在对应的目录下。
|
||||
|
||||
## 重要提示
|
||||
|
||||
> **警告**: 对于自定义开发板,当IO配置与原有开发板不同时,切勿直接覆盖原有开发板的配置编译固件。必须创建新的开发板类型,或者通过config.json文件中的builds配置不同的name和sdkconfig宏定义来区分。使用 `python scripts/release.py [开发板目录名字]` 来编译打包固件。
|
||||
>
|
||||
> 如果直接覆盖原有配置,将来OTA升级时,您的自定义固件可能会被原有开发板的标准固件覆盖,导致您的设备无法正常工作。每个开发板有唯一的标识和对应的固件升级通道,保持开发板标识的唯一性非常重要。
|
||||
|
||||
## 目录结构
|
||||
|
||||
每个开发板的目录结构通常包含以下文件:
|
||||
|
||||
- `xxx_board.cc` - 主要的板级初始化代码,实现了板子相关的初始化和功能
|
||||
- `config.h` - 板级配置文件,定义了硬件管脚映射和其他配置项
|
||||
- `config.json` - 编译配置,指定目标芯片和特殊的编译选项
|
||||
- `README.md` - 开发板相关的说明文档
|
||||
|
||||
## 定制开发板步骤
|
||||
|
||||
### 1. 创建新的开发板目录
|
||||
|
||||
首先在`boards/`目录下创建一个新的目录,例如`my-custom-board/`:
|
||||
|
||||
```bash
|
||||
mkdir main/boards/my-custom-board
|
||||
```
|
||||
|
||||
### 2. 创建配置文件
|
||||
|
||||
#### config.h
|
||||
|
||||
在`config.h`中定义所有的硬件配置,包括:
|
||||
|
||||
- 音频采样率和I2S引脚配置
|
||||
- 音频编解码芯片地址和I2C引脚配置
|
||||
- 按钮和LED引脚配置
|
||||
- 显示屏参数和引脚配置
|
||||
|
||||
参考示例(来自lichuang-c3-dev):
|
||||
|
||||
```c
|
||||
#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_I2S_GPIO_MCLK GPIO_NUM_10
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
|
||||
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_13
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
// 按钮配置
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_9
|
||||
|
||||
// 显示屏配置
|
||||
#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3
|
||||
#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5
|
||||
#define DISPLAY_DC_PIN GPIO_NUM_6
|
||||
#define DISPLAY_SPI_CS_PIN GPIO_NUM_4
|
||||
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
```
|
||||
|
||||
#### config.json
|
||||
|
||||
在`config.json`中定义编译配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"target": "esp32s3", // 目标芯片型号: esp32, esp32s3, esp32c3等
|
||||
"builds": [
|
||||
{
|
||||
"name": "my-custom-board", // 开发板名称
|
||||
"sdkconfig_append": [
|
||||
// 额外需要的编译配置
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 编写板级初始化代码
|
||||
|
||||
创建一个`my_custom_board.cc`文件,实现开发板的所有初始化逻辑。
|
||||
|
||||
一个基本的开发板类定义包含以下几个部分:
|
||||
|
||||
1. **类定义**:继承自`WifiBoard`或`Ml307Board`
|
||||
2. **初始化函数**:包括I2C、显示屏、按钮、IoT等组件的初始化
|
||||
3. **虚函数重写**:如`GetAudioCodec()`、`GetDisplay()`、`GetBacklight()`等
|
||||
4. **注册开发板**:使用`DECLARE_BOARD`宏注册开发板
|
||||
|
||||
```cpp
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "mcp_server.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#define TAG "MyCustomBoard"
|
||||
|
||||
class MyCustomBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
|
||||
// I2C初始化
|
||||
void InitializeI2c() {
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// SPI初始化(用于显示屏)
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
// 按钮初始化
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
// 显示屏初始化(以ST7789为例)
|
||||
void InitializeDisplay() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
|
||||
io_config.dc_gpio_num = DISPLAY_DC_PIN;
|
||||
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(SPI2_HOST, &io_config, &panel_io));
|
||||
|
||||
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);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
// 创建显示屏对象
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT,
|
||||
DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
|
||||
DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
// MCP Tools 初始化
|
||||
void InitializeTools() {
|
||||
// 参考 MCP 文档
|
||||
}
|
||||
|
||||
public:
|
||||
// 构造函数
|
||||
MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeDisplay();
|
||||
InitializeButtons();
|
||||
InitializeTools();
|
||||
GetBacklight()->SetBrightness(100);
|
||||
}
|
||||
|
||||
// 获取音频编解码器
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
codec_i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
// 获取显示屏
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
// 获取背光控制
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
};
|
||||
|
||||
// 注册开发板
|
||||
DECLARE_BOARD(MyCustomBoard);
|
||||
```
|
||||
|
||||
### 4. 创建README.md
|
||||
|
||||
在README.md中说明开发板的特性、硬件要求、编译和烧录步骤:
|
||||
|
||||
|
||||
## 常见开发板组件
|
||||
|
||||
### 1. 显示屏
|
||||
|
||||
项目支持多种显示屏驱动,包括:
|
||||
- ST7789 (SPI)
|
||||
- ILI9341 (SPI)
|
||||
- SH8601 (QSPI)
|
||||
- 等...
|
||||
|
||||
### 2. 音频编解码器
|
||||
|
||||
支持的编解码器包括:
|
||||
- ES8311 (常用)
|
||||
- ES7210 (麦克风阵列)
|
||||
- AW88298 (功放)
|
||||
- 等...
|
||||
|
||||
### 3. 电源管理
|
||||
|
||||
一些开发板使用电源管理芯片:
|
||||
- AXP2101
|
||||
- 其他可用的PMIC
|
||||
|
||||
### 4. MCP设备控制
|
||||
|
||||
可以添加各种MCP工具,让AI能够使用:
|
||||
- Speaker (扬声器控制)
|
||||
- Screen (屏幕亮度调节)
|
||||
- Battery (电池电量读取)
|
||||
- Light (灯光控制)
|
||||
- 等...
|
||||
|
||||
## 开发板类继承关系
|
||||
|
||||
- `Board` - 基础板级类
|
||||
- `WifiBoard` - Wi-Fi连接的开发板
|
||||
- `Ml307Board` - 使用4G模块的开发板
|
||||
- `DualNetworkBoard` - 支持Wi-Fi与4G网络切换的开发板
|
||||
|
||||
## 开发技巧
|
||||
|
||||
1. **参考相似的开发板**:如果您的新开发板与现有开发板有相似之处,可以参考现有实现
|
||||
2. **分步调试**:先实现基础功能(如显示),再添加更复杂的功能(如音频)
|
||||
3. **管脚映射**:确保在config.h中正确配置所有管脚映射
|
||||
4. **检查硬件兼容性**:确认所有芯片和驱动程序的兼容性
|
||||
|
||||
## 可能遇到的问题
|
||||
|
||||
1. **显示屏不正常**:检查SPI配置、镜像设置和颜色反转设置
|
||||
2. **音频无输出**:检查I2S配置、PA使能引脚和编解码器地址
|
||||
3. **无法连接网络**:检查Wi-Fi凭据和网络配置
|
||||
4. **无法与服务器通信**:检查MQTT或WebSocket配置
|
||||
|
||||
## 参考资料
|
||||
|
||||
- ESP-IDF 文档: https://docs.espressif.com/projects/esp-idf/
|
||||
- LVGL 文档: https://docs.lvgl.io/
|
||||
- ESP-SR 文档: https://github.com/espressif/esp-sr
|
||||
@@ -1,299 +1,299 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "led/single_led.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/timers.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3_box"
|
||||
|
||||
class ATK_NoAudioCodecDuplex : public NoAudioCodec {
|
||||
public:
|
||||
ATK_NoAudioCodecDuplex(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,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
};
|
||||
|
||||
class XL9555_IN : public I2cDevice {
|
||||
public:
|
||||
XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(0x06, 0x3B);
|
||||
WriteReg(0x07, 0xFE);
|
||||
}
|
||||
|
||||
void xl9555_cfg(void) {
|
||||
WriteReg(0x06, 0x1B);
|
||||
WriteReg(0x07, 0xFE);
|
||||
}
|
||||
|
||||
void SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint16_t data;
|
||||
int index = bit;
|
||||
|
||||
if (bit < 8) {
|
||||
data = ReadReg(0x02);
|
||||
} else {
|
||||
data = ReadReg(0x03);
|
||||
index -= 8;
|
||||
}
|
||||
|
||||
data = (data & ~(1 << index)) | (level << index);
|
||||
|
||||
if (bit < 8) {
|
||||
WriteReg(0x02, data);
|
||||
} else {
|
||||
WriteReg(0x03, data);
|
||||
}
|
||||
}
|
||||
|
||||
int GetPingState(uint16_t pin) {
|
||||
uint8_t data;
|
||||
if (pin <= 0x0080) {
|
||||
data = ReadReg(0x00);
|
||||
return (data & (uint8_t)(pin & 0xFF)) ? 1 : 0;
|
||||
} else {
|
||||
data = ReadReg(0x01);
|
||||
return (data & (uint8_t)((pin >> 8) & 0xFF )) ? 1 : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class atk_dnesp32s3_box : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
i2c_master_dev_handle_t xl9555_handle_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
XL9555_IN* xl9555_in_;
|
||||
bool es8311_detected_ = false;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)0,
|
||||
.sda_io_num = GPIO_NUM_48,
|
||||
.scl_io_num = GPIO_NUM_45,
|
||||
.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_));
|
||||
|
||||
// Initialize XL9555
|
||||
xl9555_in_ = new XL9555_IN(i2c_bus_, 0x20);
|
||||
|
||||
if (xl9555_in_->GetPingState(0x0020) == 1) {
|
||||
es8311_detected_ = true; /* 音频设备标志位,SPK_CTRL_IO为高电平时,该标志位置1,且判定为ES8311 */
|
||||
} else {
|
||||
es8311_detected_ = false; /* 音频设备标志位,SPK_CTRL_IO为低电平时,该标志位置0,且判定为NS4168 */
|
||||
}
|
||||
|
||||
xl9555_in_->xl9555_cfg();
|
||||
}
|
||||
|
||||
void InitializeATK_ST7789_80_Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
/* 配置RD引脚 */
|
||||
gpio_config_t gpio_init_struct;
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pin_bit_mask = 1ull << LCD_NUM_RD;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
gpio_set_level(LCD_NUM_RD, 1);
|
||||
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.dc_gpio_num = LCD_NUM_DC,
|
||||
.wr_gpio_num = LCD_NUM_WR,
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.data_gpio_nums = {
|
||||
GPIO_LCD_D0,
|
||||
GPIO_LCD_D1,
|
||||
GPIO_LCD_D2,
|
||||
GPIO_LCD_D3,
|
||||
GPIO_LCD_D4,
|
||||
GPIO_LCD_D5,
|
||||
GPIO_LCD_D6,
|
||||
GPIO_LCD_D7,
|
||||
},
|
||||
.bus_width = 8,
|
||||
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
|
||||
.psram_trans_align = 64,
|
||||
.sram_trans_align = 4,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
esp_lcd_panel_io_i80_config_t io_config = {
|
||||
.cs_gpio_num = LCD_NUM_CS,
|
||||
.pclk_hz = (10 * 1000 * 1000),
|
||||
.trans_queue_depth = 10,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.dc_levels = {
|
||||
.dc_idle_level = 0,
|
||||
.dc_cmd_level = 0,
|
||||
.dc_dummy_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.flags = {
|
||||
.swap_color_bytes = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = LCD_NUM_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
esp_lcd_panel_set_gap(panel, 0, 0);
|
||||
uint8_t data0[] = {0x00};
|
||||
uint8_t data1[] = {0x65};
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x36, data0, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x3A, data1, 1);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeATK_ST7789_80_Display();
|
||||
xl9555_in_->SetOutputState(5, 1);
|
||||
xl9555_in_->SetOutputState(7, 1);
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
/* 根据探测结果初始化编解码器 */
|
||||
if (es8311_detected_) {
|
||||
/* 使用ES8311 驱动 */
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
AUDIO_I2S_GPIO_DIN,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
} else {
|
||||
static ATK_NoAudioCodecDuplex 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);
|
||||
return &audio_codec;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box);
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "led/single_led.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/timers.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3_box"
|
||||
|
||||
class ATK_NoAudioCodecDuplex : public NoAudioCodec {
|
||||
public:
|
||||
ATK_NoAudioCodecDuplex(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,
|
||||
.role = I2S_ROLE_MASTER,
|
||||
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
|
||||
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
|
||||
.auto_clear_after_cb = true,
|
||||
.auto_clear_before_cb = false,
|
||||
.intr_priority = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = {
|
||||
.sample_rate_hz = (uint32_t)output_sample_rate_,
|
||||
.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.ext_clk_freq_hz = 0,
|
||||
#endif
|
||||
},
|
||||
.slot_cfg = {
|
||||
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
|
||||
.slot_mode = I2S_SLOT_MODE_STEREO,
|
||||
.slot_mask = I2S_STD_SLOT_BOTH,
|
||||
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
.ws_pol = false,
|
||||
.bit_shift = true,
|
||||
#ifdef I2S_HW_VERSION_2
|
||||
.left_align = true,
|
||||
.big_endian = false,
|
||||
.bit_order_lsb = false
|
||||
#endif
|
||||
},
|
||||
.gpio_cfg = {
|
||||
.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = bclk,
|
||||
.ws = ws,
|
||||
.dout = dout,
|
||||
.din = din,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false
|
||||
}
|
||||
}
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
};
|
||||
|
||||
class XL9555_IN : public I2cDevice {
|
||||
public:
|
||||
XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(0x06, 0x3B);
|
||||
WriteReg(0x07, 0xFE);
|
||||
}
|
||||
|
||||
void xl9555_cfg(void) {
|
||||
WriteReg(0x06, 0x1B);
|
||||
WriteReg(0x07, 0xFE);
|
||||
}
|
||||
|
||||
void SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint16_t data;
|
||||
int index = bit;
|
||||
|
||||
if (bit < 8) {
|
||||
data = ReadReg(0x02);
|
||||
} else {
|
||||
data = ReadReg(0x03);
|
||||
index -= 8;
|
||||
}
|
||||
|
||||
data = (data & ~(1 << index)) | (level << index);
|
||||
|
||||
if (bit < 8) {
|
||||
WriteReg(0x02, data);
|
||||
} else {
|
||||
WriteReg(0x03, data);
|
||||
}
|
||||
}
|
||||
|
||||
int GetPingState(uint16_t pin) {
|
||||
uint8_t data;
|
||||
if (pin <= 0x0080) {
|
||||
data = ReadReg(0x00);
|
||||
return (data & (uint8_t)(pin & 0xFF)) ? 1 : 0;
|
||||
} else {
|
||||
data = ReadReg(0x01);
|
||||
return (data & (uint8_t)((pin >> 8) & 0xFF )) ? 1 : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class atk_dnesp32s3_box : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
i2c_master_dev_handle_t xl9555_handle_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
XL9555_IN* xl9555_in_;
|
||||
bool es8311_detected_ = false;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)0,
|
||||
.sda_io_num = GPIO_NUM_48,
|
||||
.scl_io_num = GPIO_NUM_45,
|
||||
.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_));
|
||||
|
||||
// Initialize XL9555
|
||||
xl9555_in_ = new XL9555_IN(i2c_bus_, 0x20);
|
||||
|
||||
if (xl9555_in_->GetPingState(0x0020) == 1) {
|
||||
es8311_detected_ = true; /* 音频设备标志位,SPK_CTRL_IO为高电平时,该标志位置1,且判定为ES8311 */
|
||||
} else {
|
||||
es8311_detected_ = false; /* 音频设备标志位,SPK_CTRL_IO为低电平时,该标志位置0,且判定为NS4168 */
|
||||
}
|
||||
|
||||
xl9555_in_->xl9555_cfg();
|
||||
}
|
||||
|
||||
void InitializeATK_ST7789_80_Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
/* 配置RD引脚 */
|
||||
gpio_config_t gpio_init_struct;
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pin_bit_mask = 1ull << LCD_NUM_RD;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
gpio_set_level(LCD_NUM_RD, 1);
|
||||
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.dc_gpio_num = LCD_NUM_DC,
|
||||
.wr_gpio_num = LCD_NUM_WR,
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.data_gpio_nums = {
|
||||
GPIO_LCD_D0,
|
||||
GPIO_LCD_D1,
|
||||
GPIO_LCD_D2,
|
||||
GPIO_LCD_D3,
|
||||
GPIO_LCD_D4,
|
||||
GPIO_LCD_D5,
|
||||
GPIO_LCD_D6,
|
||||
GPIO_LCD_D7,
|
||||
},
|
||||
.bus_width = 8,
|
||||
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
|
||||
.psram_trans_align = 64,
|
||||
.sram_trans_align = 4,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
esp_lcd_panel_io_i80_config_t io_config = {
|
||||
.cs_gpio_num = LCD_NUM_CS,
|
||||
.pclk_hz = (10 * 1000 * 1000),
|
||||
.trans_queue_depth = 10,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.dc_levels = {
|
||||
.dc_idle_level = 0,
|
||||
.dc_cmd_level = 0,
|
||||
.dc_dummy_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.flags = {
|
||||
.swap_color_bytes = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = LCD_NUM_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
esp_lcd_panel_set_gap(panel, 0, 0);
|
||||
uint8_t data0[] = {0x00};
|
||||
uint8_t data1[] = {0x65};
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x36, data0, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x3A, data1, 1);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeATK_ST7789_80_Display();
|
||||
xl9555_in_->SetOutputState(5, 1);
|
||||
xl9555_in_->SetOutputState(7, 1);
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
/* 根据探测结果初始化编解码器 */
|
||||
if (es8311_detected_) {
|
||||
/* 使用ES8311 驱动 */
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
AUDIO_I2S_GPIO_DIN,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
} else {
|
||||
static ATK_NoAudioCodecDuplex 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);
|
||||
return &audio_codec;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box);
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
#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_I2S_GPIO_WS GPIO_NUM_13
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_47
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_4
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_SWAP_XY true
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
|
||||
// Pin Definitions
|
||||
#define LCD_NUM_CS GPIO_NUM_1
|
||||
#define LCD_NUM_DC GPIO_NUM_2
|
||||
#define LCD_NUM_RD GPIO_NUM_41
|
||||
#define LCD_NUM_WR GPIO_NUM_42
|
||||
#define LCD_NUM_RST GPIO_NUM_NC
|
||||
|
||||
#define GPIO_LCD_D0 GPIO_NUM_40
|
||||
#define GPIO_LCD_D1 GPIO_NUM_39
|
||||
#define GPIO_LCD_D2 GPIO_NUM_38
|
||||
#define GPIO_LCD_D3 GPIO_NUM_12
|
||||
#define GPIO_LCD_D4 GPIO_NUM_11
|
||||
#define GPIO_LCD_D5 GPIO_NUM_10
|
||||
#define GPIO_LCD_D6 GPIO_NUM_9
|
||||
#define GPIO_LCD_D7 GPIO_NUM_46
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
#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_I2S_GPIO_WS GPIO_NUM_13
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_47
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_4
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_SWAP_XY true
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
|
||||
// Pin Definitions
|
||||
#define LCD_NUM_CS GPIO_NUM_1
|
||||
#define LCD_NUM_DC GPIO_NUM_2
|
||||
#define LCD_NUM_RD GPIO_NUM_41
|
||||
#define LCD_NUM_WR GPIO_NUM_42
|
||||
#define LCD_NUM_RST GPIO_NUM_NC
|
||||
|
||||
#define GPIO_LCD_D0 GPIO_NUM_40
|
||||
#define GPIO_LCD_D1 GPIO_NUM_39
|
||||
#define GPIO_LCD_D2 GPIO_NUM_38
|
||||
#define GPIO_LCD_D3 GPIO_NUM_12
|
||||
#define GPIO_LCD_D4 GPIO_NUM_11
|
||||
#define GPIO_LCD_D5 GPIO_NUM_10
|
||||
#define GPIO_LCD_D6 GPIO_NUM_9
|
||||
#define GPIO_LCD_D7 GPIO_NUM_46
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,388 +1,388 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "power_manager.h"
|
||||
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3_box0"
|
||||
|
||||
class atk_dnesp32s3_box0 : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button right_button_;
|
||||
Button left_button_;
|
||||
Button middle_button_;
|
||||
LcdDisplay* display_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
PowerSupply power_status_;
|
||||
LcdStatus LcdStatus_ = kDevicelcdbacklightOn;
|
||||
PowerSleep power_sleep_ = kDeviceNoSleep;
|
||||
WakeStatus wake_status_ = kDeviceAwakened;
|
||||
XiaozhiStatus XiaozhiStatus_ = kDevice_Exit_Distributionnetwork;
|
||||
esp_timer_handle_t wake_timer_handle_;
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
|
||||
int ticks_ = 0;
|
||||
const int kChgCtrlInterval = 5;
|
||||
|
||||
void InitializeBoardPowerManager() {
|
||||
gpio_config_t gpio_init_struct = {0};
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pin_bit_mask = (1ull << CODEC_PWR_PIN) | (1ull << SYS_POW_PIN);
|
||||
gpio_config(&gpio_init_struct);
|
||||
|
||||
gpio_set_level(CODEC_PWR_PIN, 1);
|
||||
gpio_set_level(SYS_POW_PIN, 1);
|
||||
|
||||
gpio_config_t chg_init_struct = {0};
|
||||
|
||||
chg_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
chg_init_struct.mode = GPIO_MODE_INPUT;
|
||||
chg_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
chg_init_struct.pin_bit_mask = 1ull << CHRG_PIN;
|
||||
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
|
||||
|
||||
chg_init_struct.mode = GPIO_MODE_OUTPUT;
|
||||
chg_init_struct.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
chg_init_struct.pin_bit_mask = 1ull << CHG_CTRL_PIN;
|
||||
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
|
||||
gpio_set_level(CHG_CTRL_PIN, 1);
|
||||
|
||||
if (gpio_get_level(CHRG_PIN) == 0) {
|
||||
power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
esp_timer_create_args_t wake_display_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
atk_dnesp32s3_box0* self = static_cast<atk_dnesp32s3_box0*>(arg);
|
||||
if (self->LcdStatus_ == kDevicelcdbacklightOff && Application::GetInstance().GetDeviceState() == kDeviceStateListening
|
||||
&& self->wake_status_ == kDeviceWaitWake) {
|
||||
|
||||
if (self->power_sleep_ == kDeviceNeutralSleep) {
|
||||
self->power_save_timer_->WakeUp();
|
||||
}
|
||||
|
||||
self->GetBacklight()->RestoreBrightness();
|
||||
self->wake_status_ = kDeviceAwakened;
|
||||
self->LcdStatus_ = kDevicelcdbacklightOn;
|
||||
} else if (self->power_sleep_ == kDeviceNeutralSleep && Application::GetInstance().GetDeviceState() == kDeviceStateListening
|
||||
&& self->LcdStatus_ != kDevicelcdbacklightOff && self->wake_status_ == kDeviceAwakened) {
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->power_sleep_ = kDeviceNoSleep;
|
||||
} else {
|
||||
self->ticks_ ++;
|
||||
if (self->ticks_ % self->kChgCtrlInterval == 0) {
|
||||
if (gpio_get_level(CHRG_PIN) == 0) {
|
||||
self->power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
self->power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
if (self->power_manager_->low_voltage_ < 2877 && self->power_status_ != kDeviceTypecSupply) {
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(SYS_POW_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "wake_update_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 300000));
|
||||
}
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(CHRG_PIN);
|
||||
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
|
||||
if (is_charging) {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
power_sleep_ = kDeviceNeutralSleep;
|
||||
XiaozhiStatus_ = kDevice_join_Sleep;
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
|
||||
if (LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
GetBacklight()->SetBrightness(1);
|
||||
}
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
|
||||
if (XiaozhiStatus_ != kDevice_Exit_Sleep) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
if (power_status_ == kDeviceBatterySupply) {
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(SYS_POW_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
});
|
||||
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
// Initialize I2C peripheral
|
||||
void InitializeI2c() {
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
middle_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
|
||||
if (LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
if (power_sleep_ == kDeviceNeutralSleep) {
|
||||
power_save_timer_->WakeUp();
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
}
|
||||
|
||||
app.ToggleChatState();
|
||||
}
|
||||
});
|
||||
|
||||
middle_button_.OnPressUp([this]() {
|
||||
if (LcdStatus_ == kDevicelcdbacklightOff) {
|
||||
Application::GetInstance().StopListening();
|
||||
Application::GetInstance().SetDeviceState(kDeviceStateIdle);
|
||||
wake_status_ = kDeviceWaitWake;
|
||||
}
|
||||
|
||||
if (XiaozhiStatus_ == kDevice_Distributionnetwork || XiaozhiStatus_ == kDevice_Exit_Sleep) {
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(SYS_POW_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
} else if (XiaozhiStatus_ == kDevice_join_Sleep) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
XiaozhiStatus_ = kDevice_null;
|
||||
}
|
||||
});
|
||||
|
||||
middle_button_.OnLongPress([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
|
||||
if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
if (app.GetDeviceState() == kDeviceStateWifiConfiguring && power_status_ != kDeviceTypecSupply) {
|
||||
GetBacklight()->SetBrightness(0);
|
||||
XiaozhiStatus_ = kDevice_Distributionnetwork;
|
||||
} else if (power_status_ == kDeviceBatterySupply && LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
Application::GetInstance().StartListening();
|
||||
GetBacklight()->SetBrightness(0);
|
||||
XiaozhiStatus_ = kDevice_Exit_Sleep;
|
||||
} else if (power_status_ == kDeviceTypecSupply && LcdStatus_ == kDevicelcdbacklightOn && Application::GetInstance().GetDeviceState() != kDeviceStateStarting) {
|
||||
Application::GetInstance().StartListening();
|
||||
GetBacklight()->SetBrightness(0);
|
||||
LcdStatus_ = kDevicelcdbacklightOff;
|
||||
} else if (LcdStatus_ == kDevicelcdbacklightOff && (power_status_ == kDeviceTypecSupply || power_status_ == kDeviceBatterySupply)) {
|
||||
GetDisplay()->SetChatMessage("system", "");
|
||||
GetBacklight()->RestoreBrightness();
|
||||
wake_status_ = kDeviceAwakened;
|
||||
LcdStatus_ = kDevicelcdbacklightOn;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
left_button_.OnClick([this]() {
|
||||
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
power_save_timer_->WakeUp();
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
}
|
||||
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
left_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
});
|
||||
|
||||
right_button_.OnClick([this]() {
|
||||
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
power_save_timer_->WakeUp();
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
}
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
right_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 80 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
|
||||
|
||||
ESP_LOGI(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = LCD_RST_PIN;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box0() :
|
||||
right_button_(R_BUTTON_GPIO, false),
|
||||
left_button_(L_BUTTON_GPIO, false),
|
||||
middle_button_(M_BUTTON_GPIO, true) {
|
||||
InitializeBoardPowerManager();
|
||||
InitializePowerManager();
|
||||
InitializePowerSaveTimer();
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
AUDIO_I2S_GPIO_DIN,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box0);
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "power_manager.h"
|
||||
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3_box0"
|
||||
|
||||
class atk_dnesp32s3_box0 : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button right_button_;
|
||||
Button left_button_;
|
||||
Button middle_button_;
|
||||
LcdDisplay* display_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
PowerSupply power_status_;
|
||||
LcdStatus LcdStatus_ = kDevicelcdbacklightOn;
|
||||
PowerSleep power_sleep_ = kDeviceNoSleep;
|
||||
WakeStatus wake_status_ = kDeviceAwakened;
|
||||
XiaozhiStatus XiaozhiStatus_ = kDevice_Exit_Distributionnetwork;
|
||||
esp_timer_handle_t wake_timer_handle_;
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
|
||||
int ticks_ = 0;
|
||||
const int kChgCtrlInterval = 5;
|
||||
|
||||
void InitializeBoardPowerManager() {
|
||||
gpio_config_t gpio_init_struct = {0};
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pin_bit_mask = (1ull << CODEC_PWR_PIN) | (1ull << SYS_POW_PIN);
|
||||
gpio_config(&gpio_init_struct);
|
||||
|
||||
gpio_set_level(CODEC_PWR_PIN, 1);
|
||||
gpio_set_level(SYS_POW_PIN, 1);
|
||||
|
||||
gpio_config_t chg_init_struct = {0};
|
||||
|
||||
chg_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
chg_init_struct.mode = GPIO_MODE_INPUT;
|
||||
chg_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
chg_init_struct.pin_bit_mask = 1ull << CHRG_PIN;
|
||||
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
|
||||
|
||||
chg_init_struct.mode = GPIO_MODE_OUTPUT;
|
||||
chg_init_struct.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
chg_init_struct.pin_bit_mask = 1ull << CHG_CTRL_PIN;
|
||||
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
|
||||
gpio_set_level(CHG_CTRL_PIN, 1);
|
||||
|
||||
if (gpio_get_level(CHRG_PIN) == 0) {
|
||||
power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
esp_timer_create_args_t wake_display_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
atk_dnesp32s3_box0* self = static_cast<atk_dnesp32s3_box0*>(arg);
|
||||
if (self->LcdStatus_ == kDevicelcdbacklightOff && Application::GetInstance().GetDeviceState() == kDeviceStateListening
|
||||
&& self->wake_status_ == kDeviceWaitWake) {
|
||||
|
||||
if (self->power_sleep_ == kDeviceNeutralSleep) {
|
||||
self->power_save_timer_->WakeUp();
|
||||
}
|
||||
|
||||
self->GetBacklight()->RestoreBrightness();
|
||||
self->wake_status_ = kDeviceAwakened;
|
||||
self->LcdStatus_ = kDevicelcdbacklightOn;
|
||||
} else if (self->power_sleep_ == kDeviceNeutralSleep && Application::GetInstance().GetDeviceState() == kDeviceStateListening
|
||||
&& self->LcdStatus_ != kDevicelcdbacklightOff && self->wake_status_ == kDeviceAwakened) {
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->power_sleep_ = kDeviceNoSleep;
|
||||
} else {
|
||||
self->ticks_ ++;
|
||||
if (self->ticks_ % self->kChgCtrlInterval == 0) {
|
||||
if (gpio_get_level(CHRG_PIN) == 0) {
|
||||
self->power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
self->power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
if (self->power_manager_->low_voltage_ < 2877 && self->power_status_ != kDeviceTypecSupply) {
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(SYS_POW_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "wake_update_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 300000));
|
||||
}
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(CHRG_PIN);
|
||||
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
|
||||
if (is_charging) {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
power_sleep_ = kDeviceNeutralSleep;
|
||||
XiaozhiStatus_ = kDevice_join_Sleep;
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
|
||||
if (LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
GetBacklight()->SetBrightness(1);
|
||||
}
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
|
||||
if (XiaozhiStatus_ != kDevice_Exit_Sleep) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
if (power_status_ == kDeviceBatterySupply) {
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(SYS_POW_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
});
|
||||
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
// Initialize I2C peripheral
|
||||
void InitializeI2c() {
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
middle_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
|
||||
if (LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
if (power_sleep_ == kDeviceNeutralSleep) {
|
||||
power_save_timer_->WakeUp();
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
}
|
||||
|
||||
app.ToggleChatState();
|
||||
}
|
||||
});
|
||||
|
||||
middle_button_.OnPressUp([this]() {
|
||||
if (LcdStatus_ == kDevicelcdbacklightOff) {
|
||||
Application::GetInstance().StopListening();
|
||||
Application::GetInstance().SetDeviceState(kDeviceStateIdle);
|
||||
wake_status_ = kDeviceWaitWake;
|
||||
}
|
||||
|
||||
if (XiaozhiStatus_ == kDevice_Distributionnetwork || XiaozhiStatus_ == kDevice_Exit_Sleep) {
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(SYS_POW_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
} else if (XiaozhiStatus_ == kDevice_join_Sleep) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
XiaozhiStatus_ = kDevice_null;
|
||||
}
|
||||
});
|
||||
|
||||
middle_button_.OnLongPress([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
|
||||
if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
if (app.GetDeviceState() == kDeviceStateWifiConfiguring && power_status_ != kDeviceTypecSupply) {
|
||||
GetBacklight()->SetBrightness(0);
|
||||
XiaozhiStatus_ = kDevice_Distributionnetwork;
|
||||
} else if (power_status_ == kDeviceBatterySupply && LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
Application::GetInstance().StartListening();
|
||||
GetBacklight()->SetBrightness(0);
|
||||
XiaozhiStatus_ = kDevice_Exit_Sleep;
|
||||
} else if (power_status_ == kDeviceTypecSupply && LcdStatus_ == kDevicelcdbacklightOn && Application::GetInstance().GetDeviceState() != kDeviceStateStarting) {
|
||||
Application::GetInstance().StartListening();
|
||||
GetBacklight()->SetBrightness(0);
|
||||
LcdStatus_ = kDevicelcdbacklightOff;
|
||||
} else if (LcdStatus_ == kDevicelcdbacklightOff && (power_status_ == kDeviceTypecSupply || power_status_ == kDeviceBatterySupply)) {
|
||||
GetDisplay()->SetChatMessage("system", "");
|
||||
GetBacklight()->RestoreBrightness();
|
||||
wake_status_ = kDeviceAwakened;
|
||||
LcdStatus_ = kDevicelcdbacklightOn;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
left_button_.OnClick([this]() {
|
||||
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
power_save_timer_->WakeUp();
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
}
|
||||
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
left_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
});
|
||||
|
||||
right_button_.OnClick([this]() {
|
||||
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
|
||||
power_save_timer_->WakeUp();
|
||||
power_sleep_ = kDeviceNoSleep;
|
||||
}
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
right_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 80 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
|
||||
|
||||
ESP_LOGI(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = LCD_RST_PIN;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box0() :
|
||||
right_button_(R_BUTTON_GPIO, false),
|
||||
left_button_(L_BUTTON_GPIO, false),
|
||||
middle_button_(M_BUTTON_GPIO, true) {
|
||||
InitializeBoardPowerManager();
|
||||
InitializePowerManager();
|
||||
InitializePowerSaveTimer();
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
AUDIO_I2S_GPIO_DIN,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box0);
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
enum XiaozhiStatus {
|
||||
kDevice_null,
|
||||
kDevice_join_Sleep,
|
||||
kDevice_Exit_Sleep,
|
||||
kDevice_Distributionnetwork,
|
||||
kDevice_Exit_Distributionnetwork,
|
||||
};
|
||||
|
||||
enum LcdStatus {
|
||||
kDevicelcdbacklightOn,
|
||||
kDevicelcdbacklightOff,
|
||||
};
|
||||
|
||||
enum WakeStatus {
|
||||
kDeviceAwakened,
|
||||
kDeviceWaitWake,
|
||||
kDeviceSleeped,
|
||||
};
|
||||
|
||||
enum PowerSupply {
|
||||
kDeviceTypecSupply,
|
||||
kDeviceBatterySupply,
|
||||
};
|
||||
|
||||
enum PowerSleep {
|
||||
kDeviceNoSleep,
|
||||
kDeviceDeepSleep,
|
||||
kDeviceNeutralSleep,
|
||||
};
|
||||
|
||||
#define SYS_POW_PIN GPIO_NUM_2
|
||||
#define CHG_CTRL_PIN GPIO_NUM_47
|
||||
#define CODEC_PWR_PIN GPIO_NUM_14
|
||||
#define CHRG_PIN GPIO_NUM_48
|
||||
|
||||
#define BAT_VSEN_PIN GPIO_NUM_1
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_11
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define AUDIO_SPK_GPIO_PIN GPIO_NUM_21
|
||||
|
||||
#define R_BUTTON_GPIO GPIO_NUM_0
|
||||
#define M_BUTTON_GPIO GPIO_NUM_4
|
||||
#define L_BUTTON_GPIO GPIO_NUM_3
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_13
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_39
|
||||
#define LCD_MOSI_PIN GPIO_NUM_40
|
||||
#define LCD_MISO_PIN GPIO_NUM_NC
|
||||
#define LCD_DC_PIN GPIO_NUM_38
|
||||
#define LCD_CS_PIN GPIO_NUM_41
|
||||
#define LCD_RST_PIN GPIO_NUM_NC
|
||||
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
enum XiaozhiStatus {
|
||||
kDevice_null,
|
||||
kDevice_join_Sleep,
|
||||
kDevice_Exit_Sleep,
|
||||
kDevice_Distributionnetwork,
|
||||
kDevice_Exit_Distributionnetwork,
|
||||
};
|
||||
|
||||
enum LcdStatus {
|
||||
kDevicelcdbacklightOn,
|
||||
kDevicelcdbacklightOff,
|
||||
};
|
||||
|
||||
enum WakeStatus {
|
||||
kDeviceAwakened,
|
||||
kDeviceWaitWake,
|
||||
kDeviceSleeped,
|
||||
};
|
||||
|
||||
enum PowerSupply {
|
||||
kDeviceTypecSupply,
|
||||
kDeviceBatterySupply,
|
||||
};
|
||||
|
||||
enum PowerSleep {
|
||||
kDeviceNoSleep,
|
||||
kDeviceDeepSleep,
|
||||
kDeviceNeutralSleep,
|
||||
};
|
||||
|
||||
#define SYS_POW_PIN GPIO_NUM_2
|
||||
#define CHG_CTRL_PIN GPIO_NUM_47
|
||||
#define CODEC_PWR_PIN GPIO_NUM_14
|
||||
#define CHRG_PIN GPIO_NUM_48
|
||||
|
||||
#define BAT_VSEN_PIN GPIO_NUM_1
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_11
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define AUDIO_SPK_GPIO_PIN GPIO_NUM_21
|
||||
|
||||
#define R_BUTTON_GPIO GPIO_NUM_0
|
||||
#define M_BUTTON_GPIO GPIO_NUM_4
|
||||
#define L_BUTTON_GPIO GPIO_NUM_3
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_13
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_39
|
||||
#define LCD_MOSI_PIN GPIO_NUM_40
|
||||
#define LCD_MISO_PIN GPIO_NUM_NC
|
||||
#define LCD_DC_PIN GPIO_NUM_38
|
||||
#define LCD_CS_PIN GPIO_NUM_41
|
||||
#define LCD_RST_PIN GPIO_NUM_NC
|
||||
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box0",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box0",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,193 +1,193 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
bool new_charging_status = gpio_get_level(charging_pin_) == 0;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
uint32_t temp_val = 0;
|
||||
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
for(int t = 0; t < 10; t ++) {
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
|
||||
temp_val += adc_value;
|
||||
}
|
||||
|
||||
gpio_set_level(CHG_CTRL_PIN, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
adc_value = temp_val / 10;
|
||||
|
||||
// 将 ADC 值添加到队列中
|
||||
adc_values_.push_back(adc_value);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
uint32_t average_adc = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_adc += value;
|
||||
}
|
||||
average_adc /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间
|
||||
const struct {
|
||||
uint16_t adc;
|
||||
uint8_t level;
|
||||
} levels[] = {
|
||||
{2951, 0}, /* 3.80V */
|
||||
{3019, 20},
|
||||
{3037, 40},
|
||||
{3091, 60}, /* 3.88 */
|
||||
{3124, 80},
|
||||
{3231, 100}
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_adc < levels[0].adc) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_adc >= levels[5].adc) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
|
||||
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
low_voltage_ = adc_value;
|
||||
|
||||
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
uint16_t low_voltage_ = 2877;
|
||||
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
PowerManager* self = static_cast<PowerManager*>(arg);
|
||||
self->CheckBatteryStatus();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "battery_check_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
|
||||
|
||||
// 初始化 ADC
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
if (adc_handle_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
bool new_charging_status = gpio_get_level(charging_pin_) == 0;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
uint32_t temp_val = 0;
|
||||
|
||||
gpio_set_level(CHG_CTRL_PIN, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
for(int t = 0; t < 10; t ++) {
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
|
||||
temp_val += adc_value;
|
||||
}
|
||||
|
||||
gpio_set_level(CHG_CTRL_PIN, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
adc_value = temp_val / 10;
|
||||
|
||||
// 将 ADC 值添加到队列中
|
||||
adc_values_.push_back(adc_value);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
uint32_t average_adc = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_adc += value;
|
||||
}
|
||||
average_adc /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间
|
||||
const struct {
|
||||
uint16_t adc;
|
||||
uint8_t level;
|
||||
} levels[] = {
|
||||
{2951, 0}, /* 3.80V */
|
||||
{3019, 20},
|
||||
{3037, 40},
|
||||
{3091, 60}, /* 3.88 */
|
||||
{3124, 80},
|
||||
{3231, 100}
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_adc < levels[0].adc) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_adc >= levels[5].adc) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
|
||||
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
low_voltage_ = adc_value;
|
||||
|
||||
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
uint16_t low_voltage_ = 2877;
|
||||
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
PowerManager* self = static_cast<PowerManager*>(arg);
|
||||
self->CheckBatteryStatus();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "battery_check_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
|
||||
|
||||
// 初始化 ADC
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
if (adc_handle_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,477 +1,477 @@
|
||||
#include "dual_network_board.h"
|
||||
#include "codecs/es8389_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "power_manager.h"
|
||||
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
|
||||
#define TAG "atk_dnesp32s3_box2_4g"
|
||||
|
||||
class atk_dnesp32s3_box2_4g : public DualNetworkBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
LcdDisplay* display_;
|
||||
esp_io_expander_handle_t io_exp_handle;
|
||||
button_handle_t btns;
|
||||
button_driver_t* btn_driver_ = nullptr;
|
||||
static atk_dnesp32s3_box2_4g* instance_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
PowerSupply power_status_;
|
||||
esp_timer_handle_t wake_timer_handle_;
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
int ticks_ = 0;
|
||||
const int kChgCtrlInterval = 5;
|
||||
|
||||
void InitializeBoardPowerManager() {
|
||||
instance_ = this;
|
||||
|
||||
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
esp_timer_create_args_t wake_display_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
atk_dnesp32s3_box2_4g* self = static_cast<atk_dnesp32s3_box2_4g*>(arg);
|
||||
|
||||
self->ticks_ ++;
|
||||
if (self->ticks_ % self->kChgCtrlInterval == 0) {
|
||||
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
self->power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
self->power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
/* 低于某个电量,会自动关机 */
|
||||
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "wake_update_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
|
||||
}
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(io_exp_handle);
|
||||
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
|
||||
if (is_charging) {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
GetBacklight()->SetBrightness(1);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
GetBacklight()->RestoreBrightness();
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
if (power_status_ == kDeviceBatterySupply) {
|
||||
GetBacklight()->SetBrightness(0);
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
|
||||
}
|
||||
});
|
||||
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void audio_volume_change(bool direction) {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume();
|
||||
|
||||
if (direction) {
|
||||
volume += 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
} else {
|
||||
volume -= 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
}
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
}
|
||||
|
||||
void audio_volume_minimum(){
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
}
|
||||
|
||||
void audio_volume_maxmum(){
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
}
|
||||
|
||||
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
|
||||
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
|
||||
}
|
||||
|
||||
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
|
||||
uint32_t pin_val = 0;
|
||||
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
pin_mask &= DRV_IO_EXP_INPUT_MASK;
|
||||
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
|
||||
}
|
||||
|
||||
void InitializeIoExpander() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
|
||||
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
|
||||
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
|
||||
|
||||
assert(ret == ESP_OK);
|
||||
}
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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() {
|
||||
instance_ = this;
|
||||
|
||||
button_config_t l_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t m_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t r_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_driver_t* xio_l_btn_driver_ = nullptr;
|
||||
button_driver_t* xio_m_btn_driver_ = nullptr;
|
||||
|
||||
button_handle_t l_btn_handle = NULL;
|
||||
button_handle_t m_btn_handle = NULL;
|
||||
button_handle_t r_btn_handle = NULL;
|
||||
|
||||
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_l_btn_driver_->enable_power_save = false;
|
||||
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
|
||||
|
||||
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_m_btn_driver_->enable_power_save = false;
|
||||
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return instance_->IoExpanderGetLevel(XIO_KEY_M);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
|
||||
|
||||
button_gpio_config_t r_cfg = {
|
||||
.gpio_num = R_BUTTON_GPIO,
|
||||
.active_level = BUTTON_INACTIVE,
|
||||
.enable_power_save = false,
|
||||
.disable_pull = false
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(false);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_minimum();
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (self->GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
}
|
||||
else {
|
||||
app.ToggleChatState();
|
||||
}
|
||||
} else {
|
||||
app.ToggleChatState();
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
self->SwitchNetworkType();
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (self->GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
if (self->power_status_ == kDeviceBatterySupply) {
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
backlight->SetBrightness(0);
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(true);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_maxmum();
|
||||
}, this);
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
/*RD PIN */
|
||||
gpio_config_t gpio_init_struct;
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
gpio_set_level(LCD_PIN_RD, 1);
|
||||
|
||||
/* BL PIN */
|
||||
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.dc_gpio_num = LCD_PIN_DC,
|
||||
.wr_gpio_num = LCD_PIN_WR,
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.data_gpio_nums = {
|
||||
LCD_PIN_D0,
|
||||
LCD_PIN_D1,
|
||||
LCD_PIN_D2,
|
||||
LCD_PIN_D3,
|
||||
LCD_PIN_D4,
|
||||
LCD_PIN_D5,
|
||||
LCD_PIN_D6,
|
||||
LCD_PIN_D7,
|
||||
},
|
||||
.bus_width = 8,
|
||||
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
|
||||
.psram_trans_align = 64,
|
||||
.sram_trans_align = 4,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
esp_lcd_panel_io_i80_config_t io_config = {
|
||||
.cs_gpio_num = LCD_PIN_CS,
|
||||
.pclk_hz = (20 * 1000 * 1000),
|
||||
.trans_queue_depth = 7,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.dc_levels = {
|
||||
.dc_idle_level = 1,
|
||||
.dc_cmd_level = 0,
|
||||
.dc_dummy_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.flags = {
|
||||
.cs_active_high = 0,
|
||||
.pclk_active_neg = 0,
|
||||
.pclk_idle_low = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = LCD_PIN_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_set_gap(panel, 0, 0);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box2_4g() :
|
||||
DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) {
|
||||
InitializeI2c();
|
||||
InitializeIoExpander();
|
||||
InitializePowerSaveTimer();
|
||||
InitializePowerManager();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
InitializeBoardPowerManager();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8389AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8389_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
DualNetworkBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box2_4g);
|
||||
|
||||
// 定义静态成员变量
|
||||
atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr;
|
||||
#include "dual_network_board.h"
|
||||
#include "codecs/es8389_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "power_manager.h"
|
||||
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
|
||||
#define TAG "atk_dnesp32s3_box2_4g"
|
||||
|
||||
class atk_dnesp32s3_box2_4g : public DualNetworkBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
LcdDisplay* display_;
|
||||
esp_io_expander_handle_t io_exp_handle;
|
||||
button_handle_t btns;
|
||||
button_driver_t* btn_driver_ = nullptr;
|
||||
static atk_dnesp32s3_box2_4g* instance_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
PowerSupply power_status_;
|
||||
esp_timer_handle_t wake_timer_handle_;
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
int ticks_ = 0;
|
||||
const int kChgCtrlInterval = 5;
|
||||
|
||||
void InitializeBoardPowerManager() {
|
||||
instance_ = this;
|
||||
|
||||
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
esp_timer_create_args_t wake_display_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
atk_dnesp32s3_box2_4g* self = static_cast<atk_dnesp32s3_box2_4g*>(arg);
|
||||
|
||||
self->ticks_ ++;
|
||||
if (self->ticks_ % self->kChgCtrlInterval == 0) {
|
||||
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
self->power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
self->power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
/* 低于某个电量,会自动关机 */
|
||||
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "wake_update_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
|
||||
}
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(io_exp_handle);
|
||||
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
|
||||
if (is_charging) {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
GetBacklight()->SetBrightness(1);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
GetBacklight()->RestoreBrightness();
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
if (power_status_ == kDeviceBatterySupply) {
|
||||
GetBacklight()->SetBrightness(0);
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
|
||||
}
|
||||
});
|
||||
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void audio_volume_change(bool direction) {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume();
|
||||
|
||||
if (direction) {
|
||||
volume += 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
} else {
|
||||
volume -= 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
}
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
}
|
||||
|
||||
void audio_volume_minimum(){
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
}
|
||||
|
||||
void audio_volume_maxmum(){
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
}
|
||||
|
||||
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
|
||||
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
|
||||
}
|
||||
|
||||
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
|
||||
uint32_t pin_val = 0;
|
||||
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
pin_mask &= DRV_IO_EXP_INPUT_MASK;
|
||||
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
|
||||
}
|
||||
|
||||
void InitializeIoExpander() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
|
||||
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
|
||||
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
|
||||
|
||||
assert(ret == ESP_OK);
|
||||
}
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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() {
|
||||
instance_ = this;
|
||||
|
||||
button_config_t l_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t m_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t r_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_driver_t* xio_l_btn_driver_ = nullptr;
|
||||
button_driver_t* xio_m_btn_driver_ = nullptr;
|
||||
|
||||
button_handle_t l_btn_handle = NULL;
|
||||
button_handle_t m_btn_handle = NULL;
|
||||
button_handle_t r_btn_handle = NULL;
|
||||
|
||||
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_l_btn_driver_->enable_power_save = false;
|
||||
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
|
||||
|
||||
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_m_btn_driver_->enable_power_save = false;
|
||||
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return instance_->IoExpanderGetLevel(XIO_KEY_M);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
|
||||
|
||||
button_gpio_config_t r_cfg = {
|
||||
.gpio_num = R_BUTTON_GPIO,
|
||||
.active_level = BUTTON_INACTIVE,
|
||||
.enable_power_save = false,
|
||||
.disable_pull = false
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(false);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_minimum();
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (self->GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
}
|
||||
else {
|
||||
app.ToggleChatState();
|
||||
}
|
||||
} else {
|
||||
app.ToggleChatState();
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
self->SwitchNetworkType();
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (self->GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
if (self->power_status_ == kDeviceBatterySupply) {
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
backlight->SetBrightness(0);
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(true);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_maxmum();
|
||||
}, this);
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
/*RD PIN */
|
||||
gpio_config_t gpio_init_struct;
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
gpio_set_level(LCD_PIN_RD, 1);
|
||||
|
||||
/* BL PIN */
|
||||
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.dc_gpio_num = LCD_PIN_DC,
|
||||
.wr_gpio_num = LCD_PIN_WR,
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.data_gpio_nums = {
|
||||
LCD_PIN_D0,
|
||||
LCD_PIN_D1,
|
||||
LCD_PIN_D2,
|
||||
LCD_PIN_D3,
|
||||
LCD_PIN_D4,
|
||||
LCD_PIN_D5,
|
||||
LCD_PIN_D6,
|
||||
LCD_PIN_D7,
|
||||
},
|
||||
.bus_width = 8,
|
||||
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
|
||||
.psram_trans_align = 64,
|
||||
.sram_trans_align = 4,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
esp_lcd_panel_io_i80_config_t io_config = {
|
||||
.cs_gpio_num = LCD_PIN_CS,
|
||||
.pclk_hz = (20 * 1000 * 1000),
|
||||
.trans_queue_depth = 7,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.dc_levels = {
|
||||
.dc_idle_level = 1,
|
||||
.dc_cmd_level = 0,
|
||||
.dc_dummy_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.flags = {
|
||||
.cs_active_high = 0,
|
||||
.pclk_active_neg = 0,
|
||||
.pclk_idle_low = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = LCD_PIN_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_set_gap(panel, 0, 0);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box2_4g() :
|
||||
DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) {
|
||||
InitializeI2c();
|
||||
InitializeIoExpander();
|
||||
InitializePowerSaveTimer();
|
||||
InitializePowerManager();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
InitializeBoardPowerManager();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8389AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8389_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
DualNetworkBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box2_4g);
|
||||
|
||||
// 定义静态成员变量
|
||||
atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr;
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
enum PowerSupply {
|
||||
kDeviceTypecSupply,
|
||||
kDeviceBatterySupply,
|
||||
};
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
|
||||
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define SPISD_PIN_MOSI GPIO_NUM_16
|
||||
#define SPISD_PIN_MISO GPIO_NUM_18
|
||||
#define SPISD_PIN_CLK GPIO_NUM_17
|
||||
#define SPISD_PIN_TS GPIO_NUM_15
|
||||
|
||||
#define R_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define XL9555_INT_GPIO GPIO_NUM_2
|
||||
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
|
||||
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
|
||||
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
|
||||
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
|
||||
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
|
||||
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
|
||||
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
|
||||
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
|
||||
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
|
||||
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
|
||||
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
|
||||
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
|
||||
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
|
||||
|
||||
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
|
||||
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
|
||||
|
||||
#define LCD_PIN_CS GPIO_NUM_14
|
||||
#define LCD_PIN_DC GPIO_NUM_12
|
||||
#define LCD_PIN_RD GPIO_NUM_10
|
||||
#define LCD_PIN_WR GPIO_NUM_11
|
||||
#define LCD_PIN_RST GPIO_NUM_NC
|
||||
#define LCD_PIN_D0 GPIO_NUM_13
|
||||
#define LCD_PIN_D1 GPIO_NUM_9
|
||||
#define LCD_PIN_D2 GPIO_NUM_8
|
||||
#define LCD_PIN_D3 GPIO_NUM_7
|
||||
#define LCD_PIN_D4 GPIO_NUM_6
|
||||
#define LCD_PIN_D5 GPIO_NUM_5
|
||||
#define LCD_PIN_D6 GPIO_NUM_4
|
||||
#define LCD_PIN_D7 GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#define Module_4G_RX_PIN GPIO_NUM_44
|
||||
#define Module_4G_TX_PIN GPIO_NUM_43
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
enum PowerSupply {
|
||||
kDeviceTypecSupply,
|
||||
kDeviceBatterySupply,
|
||||
};
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
|
||||
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define SPISD_PIN_MOSI GPIO_NUM_16
|
||||
#define SPISD_PIN_MISO GPIO_NUM_18
|
||||
#define SPISD_PIN_CLK GPIO_NUM_17
|
||||
#define SPISD_PIN_TS GPIO_NUM_15
|
||||
|
||||
#define R_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define XL9555_INT_GPIO GPIO_NUM_2
|
||||
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
|
||||
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
|
||||
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
|
||||
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
|
||||
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
|
||||
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
|
||||
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
|
||||
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
|
||||
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
|
||||
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
|
||||
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
|
||||
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
|
||||
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
|
||||
|
||||
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
|
||||
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
|
||||
|
||||
#define LCD_PIN_CS GPIO_NUM_14
|
||||
#define LCD_PIN_DC GPIO_NUM_12
|
||||
#define LCD_PIN_RD GPIO_NUM_10
|
||||
#define LCD_PIN_WR GPIO_NUM_11
|
||||
#define LCD_PIN_RST GPIO_NUM_NC
|
||||
#define LCD_PIN_D0 GPIO_NUM_13
|
||||
#define LCD_PIN_D1 GPIO_NUM_9
|
||||
#define LCD_PIN_D2 GPIO_NUM_8
|
||||
#define LCD_PIN_D3 GPIO_NUM_7
|
||||
#define LCD_PIN_D4 GPIO_NUM_6
|
||||
#define LCD_PIN_D5 GPIO_NUM_5
|
||||
#define LCD_PIN_D6 GPIO_NUM_4
|
||||
#define LCD_PIN_D7 GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#define Module_4G_RX_PIN GPIO_NUM_44
|
||||
#define Module_4G_TX_PIN GPIO_NUM_43
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box2-4g",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box2-4g",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,195 +1,195 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
esp_io_expander_handle_t xl9555_;
|
||||
uint32_t pin_val = 0;
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
uint32_t temp_val = 0;
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
for(int t = 0; t < 10; t ++) {
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
|
||||
temp_val += adc_value;
|
||||
}
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
|
||||
adc_value = temp_val / 10;
|
||||
|
||||
// 将 ADC 值添加到队列中
|
||||
adc_values_.push_back(adc_value);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
uint32_t average_adc = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_adc += value;
|
||||
}
|
||||
average_adc /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间
|
||||
const struct {
|
||||
uint16_t adc;
|
||||
uint8_t level;
|
||||
} levels[] = {
|
||||
{2696, 0}, /* 3.48V -屏幕闪屏 */
|
||||
{2724, 20}, /* 3.53V */
|
||||
{2861, 40}, /* 3.7V */
|
||||
{3038, 60}, /* 3.90V */
|
||||
{3150, 80}, /* 4.02V */
|
||||
{3280, 100} /* 4.14V */
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_adc < levels[0].adc) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_adc >= levels[5].adc) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
|
||||
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
low_voltage_ = adc_value;
|
||||
|
||||
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
uint16_t low_voltage_ = 2630;
|
||||
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
PowerManager* self = static_cast<PowerManager*>(arg);
|
||||
self->CheckBatteryStatus();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "battery_check_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
|
||||
|
||||
// 初始化 ADC
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
if (adc_handle_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
esp_io_expander_handle_t xl9555_;
|
||||
uint32_t pin_val = 0;
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
uint32_t temp_val = 0;
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
for(int t = 0; t < 10; t ++) {
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
|
||||
temp_val += adc_value;
|
||||
}
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
|
||||
adc_value = temp_val / 10;
|
||||
|
||||
// 将 ADC 值添加到队列中
|
||||
adc_values_.push_back(adc_value);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
uint32_t average_adc = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_adc += value;
|
||||
}
|
||||
average_adc /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间
|
||||
const struct {
|
||||
uint16_t adc;
|
||||
uint8_t level;
|
||||
} levels[] = {
|
||||
{2696, 0}, /* 3.48V -屏幕闪屏 */
|
||||
{2724, 20}, /* 3.53V */
|
||||
{2861, 40}, /* 3.7V */
|
||||
{3038, 60}, /* 3.90V */
|
||||
{3150, 80}, /* 4.02V */
|
||||
{3280, 100} /* 4.14V */
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_adc < levels[0].adc) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_adc >= levels[5].adc) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
|
||||
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
low_voltage_ = adc_value;
|
||||
|
||||
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
uint16_t low_voltage_ = 2630;
|
||||
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
PowerManager* self = static_cast<PowerManager*>(arg);
|
||||
self->CheckBatteryStatus();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "battery_check_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
|
||||
|
||||
// 初始化 ADC
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
if (adc_handle_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,456 +1,456 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8389_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "power_manager.h"
|
||||
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
|
||||
#define TAG "atk_dnesp32s3_box2_wifi"
|
||||
|
||||
class atk_dnesp32s3_box2_wifi : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
LcdDisplay* display_;
|
||||
esp_io_expander_handle_t io_exp_handle;
|
||||
button_handle_t btns;
|
||||
button_driver_t* btn_driver_ = nullptr;
|
||||
static atk_dnesp32s3_box2_wifi* instance_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
PowerSupply power_status_;
|
||||
esp_timer_handle_t wake_timer_handle_;
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
int ticks_ = 0;
|
||||
const int kChgCtrlInterval = 5;
|
||||
|
||||
void InitializeBoardPowerManager() {
|
||||
instance_ = this;
|
||||
|
||||
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
esp_timer_create_args_t wake_display_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
atk_dnesp32s3_box2_wifi* self = static_cast<atk_dnesp32s3_box2_wifi*>(arg);
|
||||
|
||||
self->ticks_ ++;
|
||||
if (self->ticks_ % self->kChgCtrlInterval == 0) {
|
||||
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
self->power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
self->power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
/* 低于某个电量,会自动关机 */
|
||||
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "wake_update_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
|
||||
}
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(io_exp_handle);
|
||||
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
|
||||
if (is_charging) {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
GetBacklight()->SetBrightness(1);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
GetBacklight()->RestoreBrightness();
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
if (power_status_ == kDeviceBatterySupply) {
|
||||
GetBacklight()->SetBrightness(0);
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
|
||||
}
|
||||
});
|
||||
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void audio_volume_change(bool direction) {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume();
|
||||
|
||||
if (direction) {
|
||||
volume += 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
} else {
|
||||
volume -= 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
}
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
}
|
||||
|
||||
void audio_volume_minimum(){
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
}
|
||||
|
||||
void audio_volume_maxmum(){
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
}
|
||||
|
||||
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
|
||||
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
|
||||
}
|
||||
|
||||
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
|
||||
uint32_t pin_val = 0;
|
||||
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
pin_mask &= DRV_IO_EXP_INPUT_MASK;
|
||||
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
|
||||
}
|
||||
|
||||
void InitializeIoExpander() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
|
||||
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
|
||||
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
|
||||
|
||||
assert(ret == ESP_OK);
|
||||
}
|
||||
|
||||
// Initialize I2C peripheral
|
||||
void InitializeI2c() {
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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() {
|
||||
instance_ = this;
|
||||
|
||||
button_config_t l_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t m_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t r_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_driver_t* xio_l_btn_driver_ = nullptr;
|
||||
button_driver_t* xio_m_btn_driver_ = nullptr;
|
||||
|
||||
button_handle_t l_btn_handle = NULL;
|
||||
button_handle_t m_btn_handle = NULL;
|
||||
button_handle_t r_btn_handle = NULL;
|
||||
|
||||
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_l_btn_driver_->enable_power_save = false;
|
||||
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
|
||||
|
||||
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_m_btn_driver_->enable_power_save = false;
|
||||
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return instance_->IoExpanderGetLevel(XIO_KEY_M);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
|
||||
|
||||
button_gpio_config_t r_cfg = {
|
||||
.gpio_num = R_BUTTON_GPIO,
|
||||
.active_level = BUTTON_INACTIVE,
|
||||
.enable_power_save = false,
|
||||
.disable_pull = false
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(false);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_minimum();
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
app.ToggleChatState();
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
}
|
||||
|
||||
if (self->power_status_ == kDeviceBatterySupply) {
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
backlight->SetBrightness(0);
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(true);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_maxmum();
|
||||
}, this);
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
/* RD PIN */
|
||||
gpio_config_t gpio_init_struct;
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
gpio_set_level(LCD_PIN_RD, 1);
|
||||
|
||||
/* BL PIN */
|
||||
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.dc_gpio_num = LCD_PIN_DC,
|
||||
.wr_gpio_num = LCD_PIN_WR,
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.data_gpio_nums = {
|
||||
LCD_PIN_D0,
|
||||
LCD_PIN_D1,
|
||||
LCD_PIN_D2,
|
||||
LCD_PIN_D3,
|
||||
LCD_PIN_D4,
|
||||
LCD_PIN_D5,
|
||||
LCD_PIN_D6,
|
||||
LCD_PIN_D7,
|
||||
},
|
||||
.bus_width = 8,
|
||||
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
|
||||
.psram_trans_align = 64,
|
||||
.sram_trans_align = 4,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
esp_lcd_panel_io_i80_config_t io_config = {
|
||||
.cs_gpio_num = LCD_PIN_CS,
|
||||
.pclk_hz = (20 * 1000 * 1000),
|
||||
.trans_queue_depth = 7,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.dc_levels = {
|
||||
.dc_idle_level = 1,
|
||||
.dc_cmd_level = 0,
|
||||
.dc_dummy_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.flags = {
|
||||
.cs_active_high = 0,
|
||||
.pclk_active_neg = 0,
|
||||
.pclk_idle_low = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = LCD_PIN_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_set_gap(panel, 0, 0);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box2_wifi() {
|
||||
InitializeI2c();
|
||||
InitializeIoExpander();
|
||||
InitializePowerSaveTimer();
|
||||
InitializePowerManager();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
InitializeBoardPowerManager();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8389AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8389_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box2_wifi);
|
||||
|
||||
// 定义静态成员变量
|
||||
atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8389_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "power_manager.h"
|
||||
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
|
||||
#define TAG "atk_dnesp32s3_box2_wifi"
|
||||
|
||||
class atk_dnesp32s3_box2_wifi : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
LcdDisplay* display_;
|
||||
esp_io_expander_handle_t io_exp_handle;
|
||||
button_handle_t btns;
|
||||
button_driver_t* btn_driver_ = nullptr;
|
||||
static atk_dnesp32s3_box2_wifi* instance_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
PowerSupply power_status_;
|
||||
esp_timer_handle_t wake_timer_handle_;
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
int ticks_ = 0;
|
||||
const int kChgCtrlInterval = 5;
|
||||
|
||||
void InitializeBoardPowerManager() {
|
||||
instance_ = this;
|
||||
|
||||
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
esp_timer_create_args_t wake_display_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
atk_dnesp32s3_box2_wifi* self = static_cast<atk_dnesp32s3_box2_wifi*>(arg);
|
||||
|
||||
self->ticks_ ++;
|
||||
if (self->ticks_ % self->kChgCtrlInterval == 0) {
|
||||
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
|
||||
self->power_status_ = kDeviceTypecSupply;
|
||||
} else {
|
||||
self->power_status_ = kDeviceBatterySupply;
|
||||
}
|
||||
|
||||
/* 低于某个电量,会自动关机 */
|
||||
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "wake_update_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
|
||||
}
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(io_exp_handle);
|
||||
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
|
||||
if (is_charging) {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
GetBacklight()->SetBrightness(1);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
GetBacklight()->RestoreBrightness();
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
if (power_status_ == kDeviceBatterySupply) {
|
||||
GetBacklight()->SetBrightness(0);
|
||||
esp_timer_stop(power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
|
||||
}
|
||||
});
|
||||
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void audio_volume_change(bool direction) {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume();
|
||||
|
||||
if (direction) {
|
||||
volume += 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
} else {
|
||||
volume -= 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
}
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
}
|
||||
|
||||
void audio_volume_minimum(){
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
}
|
||||
|
||||
void audio_volume_maxmum(){
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
}
|
||||
|
||||
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
|
||||
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
|
||||
}
|
||||
|
||||
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
|
||||
uint32_t pin_val = 0;
|
||||
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
pin_mask &= DRV_IO_EXP_INPUT_MASK;
|
||||
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
|
||||
}
|
||||
|
||||
void InitializeIoExpander() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
|
||||
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
|
||||
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
|
||||
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
|
||||
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
|
||||
|
||||
assert(ret == ESP_OK);
|
||||
}
|
||||
|
||||
// Initialize I2C peripheral
|
||||
void InitializeI2c() {
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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() {
|
||||
instance_ = this;
|
||||
|
||||
button_config_t l_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t m_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_config_t r_btn_cfg = {
|
||||
.long_press_time = 800,
|
||||
.short_press_time = 500
|
||||
};
|
||||
|
||||
button_driver_t* xio_l_btn_driver_ = nullptr;
|
||||
button_driver_t* xio_m_btn_driver_ = nullptr;
|
||||
|
||||
button_handle_t l_btn_handle = NULL;
|
||||
button_handle_t m_btn_handle = NULL;
|
||||
button_handle_t r_btn_handle = NULL;
|
||||
|
||||
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_l_btn_driver_->enable_power_save = false;
|
||||
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
|
||||
|
||||
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
|
||||
xio_m_btn_driver_->enable_power_save = false;
|
||||
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
|
||||
return instance_->IoExpanderGetLevel(XIO_KEY_M);
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
|
||||
|
||||
button_gpio_config_t r_cfg = {
|
||||
.gpio_num = R_BUTTON_GPIO,
|
||||
.active_level = BUTTON_INACTIVE,
|
||||
.enable_power_save = false,
|
||||
.disable_pull = false
|
||||
};
|
||||
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(false);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_minimum();
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
app.ToggleChatState();
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
}
|
||||
|
||||
if (self->power_status_ == kDeviceBatterySupply) {
|
||||
auto backlight = Board::GetInstance().GetBacklight();
|
||||
backlight->SetBrightness(0);
|
||||
esp_timer_stop(self->power_manager_->timer_handle_);
|
||||
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_change(true);
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
self->power_save_timer_->WakeUp();
|
||||
self->audio_volume_maxmum();
|
||||
}, this);
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
/* RD PIN */
|
||||
gpio_config_t gpio_init_struct;
|
||||
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
|
||||
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
gpio_set_level(LCD_PIN_RD, 1);
|
||||
|
||||
/* BL PIN */
|
||||
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
|
||||
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&gpio_init_struct);
|
||||
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.dc_gpio_num = LCD_PIN_DC,
|
||||
.wr_gpio_num = LCD_PIN_WR,
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.data_gpio_nums = {
|
||||
LCD_PIN_D0,
|
||||
LCD_PIN_D1,
|
||||
LCD_PIN_D2,
|
||||
LCD_PIN_D3,
|
||||
LCD_PIN_D4,
|
||||
LCD_PIN_D5,
|
||||
LCD_PIN_D6,
|
||||
LCD_PIN_D7,
|
||||
},
|
||||
.bus_width = 8,
|
||||
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
|
||||
.psram_trans_align = 64,
|
||||
.sram_trans_align = 4,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
esp_lcd_panel_io_i80_config_t io_config = {
|
||||
.cs_gpio_num = LCD_PIN_CS,
|
||||
.pclk_hz = (20 * 1000 * 1000),
|
||||
.trans_queue_depth = 7,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.dc_levels = {
|
||||
.dc_idle_level = 1,
|
||||
.dc_cmd_level = 0,
|
||||
.dc_dummy_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.flags = {
|
||||
.cs_active_high = 0,
|
||||
.pclk_active_neg = 0,
|
||||
.pclk_idle_low = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = LCD_PIN_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_set_gap(panel, 0, 0);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3_box2_wifi() {
|
||||
InitializeI2c();
|
||||
InitializeIoExpander();
|
||||
InitializePowerSaveTimer();
|
||||
InitializePowerManager();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
InitializeBoardPowerManager();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8389AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8389_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box2_wifi);
|
||||
|
||||
// 定义静态成员变量
|
||||
atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
enum PowerSupply {
|
||||
kDeviceTypecSupply,
|
||||
kDeviceBatterySupply,
|
||||
};
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
|
||||
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define R_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define XL9555_INT_GPIO GPIO_NUM_2
|
||||
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
|
||||
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
|
||||
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
|
||||
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
|
||||
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
|
||||
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
|
||||
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
|
||||
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
|
||||
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
|
||||
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
|
||||
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
|
||||
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
|
||||
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
|
||||
|
||||
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
|
||||
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
|
||||
|
||||
#define LCD_PIN_CS GPIO_NUM_14
|
||||
#define LCD_PIN_DC GPIO_NUM_12
|
||||
#define LCD_PIN_RD GPIO_NUM_10
|
||||
#define LCD_PIN_WR GPIO_NUM_11
|
||||
#define LCD_PIN_RST GPIO_NUM_NC
|
||||
#define LCD_PIN_D0 GPIO_NUM_13
|
||||
#define LCD_PIN_D1 GPIO_NUM_9
|
||||
#define LCD_PIN_D2 GPIO_NUM_8
|
||||
#define LCD_PIN_D3 GPIO_NUM_7
|
||||
#define LCD_PIN_D4 GPIO_NUM_6
|
||||
#define LCD_PIN_D5 GPIO_NUM_5
|
||||
#define LCD_PIN_D6 GPIO_NUM_4
|
||||
#define LCD_PIN_D7 GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
enum PowerSupply {
|
||||
kDeviceTypecSupply,
|
||||
kDeviceBatterySupply,
|
||||
};
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
|
||||
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define R_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define XL9555_INT_GPIO GPIO_NUM_2
|
||||
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
|
||||
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
|
||||
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
|
||||
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
|
||||
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
|
||||
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
|
||||
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
|
||||
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
|
||||
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
|
||||
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
|
||||
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
|
||||
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
|
||||
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
|
||||
|
||||
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
|
||||
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
|
||||
|
||||
#define LCD_PIN_CS GPIO_NUM_14
|
||||
#define LCD_PIN_DC GPIO_NUM_12
|
||||
#define LCD_PIN_RD GPIO_NUM_10
|
||||
#define LCD_PIN_WR GPIO_NUM_11
|
||||
#define LCD_PIN_RST GPIO_NUM_NC
|
||||
#define LCD_PIN_D0 GPIO_NUM_13
|
||||
#define LCD_PIN_D1 GPIO_NUM_9
|
||||
#define LCD_PIN_D2 GPIO_NUM_8
|
||||
#define LCD_PIN_D3 GPIO_NUM_7
|
||||
#define LCD_PIN_D4 GPIO_NUM_6
|
||||
#define LCD_PIN_D5 GPIO_NUM_5
|
||||
#define LCD_PIN_D6 GPIO_NUM_4
|
||||
#define LCD_PIN_D7 GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 320
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box2-wifi",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box2-wifi",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,195 +1,195 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
esp_io_expander_handle_t xl9555_;
|
||||
uint32_t pin_val = 0;
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
uint32_t temp_val = 0;
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
for(int t = 0; t < 10; t ++) {
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
|
||||
temp_val += adc_value;
|
||||
}
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
|
||||
adc_value = temp_val / 10;
|
||||
|
||||
// 将 ADC 值添加到队列中
|
||||
adc_values_.push_back(adc_value);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
uint32_t average_adc = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_adc += value;
|
||||
}
|
||||
average_adc /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间
|
||||
const struct {
|
||||
uint16_t adc;
|
||||
uint8_t level;
|
||||
} levels[] = {
|
||||
{2696, 0}, /* 3.48V -屏幕闪屏 */
|
||||
{2724, 20}, /* 3.53V */
|
||||
{2861, 40}, /* 3.7V */
|
||||
{3038, 60}, /* 3.90V */
|
||||
{3150, 80}, /* 4.02V */
|
||||
{3280, 100} /* 4.14V */
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_adc < levels[0].adc) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_adc >= levels[5].adc) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
|
||||
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
low_voltage_ = adc_value;
|
||||
|
||||
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
uint16_t low_voltage_ = 2630;
|
||||
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
PowerManager* self = static_cast<PowerManager*>(arg);
|
||||
self->CheckBatteryStatus();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "battery_check_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
|
||||
|
||||
// 初始化 ADC
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
if (adc_handle_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
esp_io_expander_handle_t xl9555_;
|
||||
uint32_t pin_val = 0;
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
|
||||
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
uint32_t temp_val = 0;
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
|
||||
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
for(int t = 0; t < 10; t ++) {
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
|
||||
temp_val += adc_value;
|
||||
}
|
||||
|
||||
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
|
||||
|
||||
adc_value = temp_val / 10;
|
||||
|
||||
// 将 ADC 值添加到队列中
|
||||
adc_values_.push_back(adc_value);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
uint32_t average_adc = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_adc += value;
|
||||
}
|
||||
average_adc /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间
|
||||
const struct {
|
||||
uint16_t adc;
|
||||
uint8_t level;
|
||||
} levels[] = {
|
||||
{2696, 0}, /* 3.48V -屏幕闪屏 */
|
||||
{2724, 20}, /* 3.53V */
|
||||
{2861, 40}, /* 3.7V */
|
||||
{3038, 60}, /* 3.90V */
|
||||
{3150, 80}, /* 4.02V */
|
||||
{3280, 100} /* 4.14V */
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_adc < levels[0].adc) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_adc >= levels[5].adc) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
|
||||
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
low_voltage_ = adc_value;
|
||||
|
||||
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
uint16_t low_voltage_ = 2630;
|
||||
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
PowerManager* self = static_cast<PowerManager*>(arg);
|
||||
self->CheckBatteryStatus();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "battery_check_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
|
||||
|
||||
// 初始化 ADC
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
if (adc_handle_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,230 +1,230 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "led/single_led.h"
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3"
|
||||
|
||||
class XL9555 : public I2cDevice {
|
||||
public:
|
||||
XL9555(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(0x06, 0x03);
|
||||
WriteReg(0x07, 0xF0);
|
||||
}
|
||||
|
||||
void SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint16_t data;
|
||||
int index = bit;
|
||||
|
||||
if (bit < 8) {
|
||||
data = ReadReg(0x02);
|
||||
} else {
|
||||
data = ReadReg(0x03);
|
||||
index -= 8;
|
||||
}
|
||||
|
||||
data = (data & ~(1 << index)) | (level << index);
|
||||
|
||||
if (bit < 8) {
|
||||
WriteReg(0x02, data);
|
||||
} else {
|
||||
WriteReg(0x03, data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class atk_dnesp32s3 : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
XL9555* xl9555_;
|
||||
Esp32Camera* camera_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
|
||||
// Initialize XL9555
|
||||
xl9555_ = new XL9555(i2c_bus_, 0x20);
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
// 液晶屏控制IO初始化
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 20 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
esp_lcd_new_panel_io_spi(SPI2_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;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
xl9555_->SetOutputState(8, 1);
|
||||
xl9555_->SetOutputState(2, 0);
|
||||
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
// 初始化摄像头:ov2640;
|
||||
// 根据正点原子官方示例参数
|
||||
void InitializeCamera() {
|
||||
|
||||
xl9555_->SetOutputState(OV_PWDN_IO, 0); // PWDN=低 (上电)
|
||||
xl9555_->SetOutputState(OV_RESET_IO, 0); // 确保复位
|
||||
vTaskDelay(pdMS_TO_TICKS(50)); // 延长复位保持时间
|
||||
xl9555_->SetOutputState(OV_RESET_IO, 1); // 释放复位
|
||||
vTaskDelay(pdMS_TO_TICKS(50)); // 延长 50ms
|
||||
|
||||
camera_config_t config = {};
|
||||
|
||||
config.pin_pwdn = CAM_PIN_PWDN; // 实际由 XL9555 控制
|
||||
config.pin_reset = CAM_PIN_RESET;// 实际由 XL9555 控制
|
||||
config.pin_xclk = CAM_PIN_XCLK;
|
||||
config.pin_sccb_sda = CAM_PIN_SIOD;
|
||||
config.pin_sccb_scl = CAM_PIN_SIOC;
|
||||
|
||||
config.pin_d7 = CAM_PIN_D7;
|
||||
config.pin_d6 = CAM_PIN_D6;
|
||||
config.pin_d5 = CAM_PIN_D5;
|
||||
config.pin_d4 = CAM_PIN_D4;
|
||||
config.pin_d3 = CAM_PIN_D3;
|
||||
config.pin_d2 = CAM_PIN_D2;
|
||||
config.pin_d1 = CAM_PIN_D1;
|
||||
config.pin_d0 = CAM_PIN_D0;
|
||||
config.pin_vsync = CAM_PIN_VSYNC;
|
||||
config.pin_href = CAM_PIN_HREF;
|
||||
config.pin_pclk = CAM_PIN_PCLK;
|
||||
|
||||
/* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */
|
||||
config.xclk_freq_hz = 24000000;
|
||||
config.ledc_timer = LEDC_TIMER_0;
|
||||
config.ledc_channel = LEDC_CHANNEL_0;
|
||||
|
||||
config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */
|
||||
config.frame_size = FRAMESIZE_QVGA; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */
|
||||
|
||||
config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */
|
||||
config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */
|
||||
config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
|
||||
esp_err_t err = esp_camera_init(&config); // 测试相机是否存在
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err));
|
||||
// 如果摄像头初始化失败,设置 camera_ 为 nullptr
|
||||
camera_ = nullptr;
|
||||
return;
|
||||
}else
|
||||
{
|
||||
esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备
|
||||
camera_ = new Esp32Camera(config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
InitializeCamera();
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8388_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3);
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "led/single_led.h"
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3"
|
||||
|
||||
class XL9555 : public I2cDevice {
|
||||
public:
|
||||
XL9555(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(0x06, 0x03);
|
||||
WriteReg(0x07, 0xF0);
|
||||
}
|
||||
|
||||
void SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint16_t data;
|
||||
int index = bit;
|
||||
|
||||
if (bit < 8) {
|
||||
data = ReadReg(0x02);
|
||||
} else {
|
||||
data = ReadReg(0x03);
|
||||
index -= 8;
|
||||
}
|
||||
|
||||
data = (data & ~(1 << index)) | (level << index);
|
||||
|
||||
if (bit < 8) {
|
||||
WriteReg(0x02, data);
|
||||
} else {
|
||||
WriteReg(0x03, data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class atk_dnesp32s3 : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
XL9555* xl9555_;
|
||||
Esp32Camera* camera_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
|
||||
// Initialize XL9555
|
||||
xl9555_ = new XL9555(i2c_bus_, 0x20);
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
// 液晶屏控制IO初始化
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 20 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
esp_lcd_new_panel_io_spi(SPI2_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;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
xl9555_->SetOutputState(8, 1);
|
||||
xl9555_->SetOutputState(2, 0);
|
||||
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
// 初始化摄像头:ov2640;
|
||||
// 根据正点原子官方示例参数
|
||||
void InitializeCamera() {
|
||||
|
||||
xl9555_->SetOutputState(OV_PWDN_IO, 0); // PWDN=低 (上电)
|
||||
xl9555_->SetOutputState(OV_RESET_IO, 0); // 确保复位
|
||||
vTaskDelay(pdMS_TO_TICKS(50)); // 延长复位保持时间
|
||||
xl9555_->SetOutputState(OV_RESET_IO, 1); // 释放复位
|
||||
vTaskDelay(pdMS_TO_TICKS(50)); // 延长 50ms
|
||||
|
||||
camera_config_t config = {};
|
||||
|
||||
config.pin_pwdn = CAM_PIN_PWDN; // 实际由 XL9555 控制
|
||||
config.pin_reset = CAM_PIN_RESET;// 实际由 XL9555 控制
|
||||
config.pin_xclk = CAM_PIN_XCLK;
|
||||
config.pin_sccb_sda = CAM_PIN_SIOD;
|
||||
config.pin_sccb_scl = CAM_PIN_SIOC;
|
||||
|
||||
config.pin_d7 = CAM_PIN_D7;
|
||||
config.pin_d6 = CAM_PIN_D6;
|
||||
config.pin_d5 = CAM_PIN_D5;
|
||||
config.pin_d4 = CAM_PIN_D4;
|
||||
config.pin_d3 = CAM_PIN_D3;
|
||||
config.pin_d2 = CAM_PIN_D2;
|
||||
config.pin_d1 = CAM_PIN_D1;
|
||||
config.pin_d0 = CAM_PIN_D0;
|
||||
config.pin_vsync = CAM_PIN_VSYNC;
|
||||
config.pin_href = CAM_PIN_HREF;
|
||||
config.pin_pclk = CAM_PIN_PCLK;
|
||||
|
||||
/* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */
|
||||
config.xclk_freq_hz = 24000000;
|
||||
config.ledc_timer = LEDC_TIMER_0;
|
||||
config.ledc_channel = LEDC_CHANNEL_0;
|
||||
|
||||
config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */
|
||||
config.frame_size = FRAMESIZE_QVGA; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */
|
||||
|
||||
config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */
|
||||
config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */
|
||||
config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
|
||||
esp_err_t err = esp_camera_init(&config); // 测试相机是否存在
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err));
|
||||
// 如果摄像头初始化失败,设置 camera_ 为 nullptr
|
||||
camera_ = nullptr;
|
||||
return;
|
||||
}else
|
||||
{
|
||||
esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备
|
||||
camera_ = new Esp32Camera(config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
InitializeCamera();
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8388_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3);
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
|
||||
#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_I2S_GPIO_MCLK GPIO_NUM_3
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_9
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
|
||||
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_1
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_12
|
||||
#define LCD_MOSI_PIN GPIO_NUM_11
|
||||
#define LCD_MISO_PIN GPIO_NUM_13
|
||||
#define LCD_DC_PIN GPIO_NUM_40
|
||||
#define LCD_CS_PIN GPIO_NUM_21
|
||||
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
/* 相机引脚配置 */
|
||||
#define CAM_PIN_PWDN GPIO_NUM_NC
|
||||
#define CAM_PIN_RESET GPIO_NUM_NC
|
||||
#define CAM_PIN_VSYNC GPIO_NUM_47
|
||||
#define CAM_PIN_HREF GPIO_NUM_48
|
||||
#define CAM_PIN_PCLK GPIO_NUM_45
|
||||
#define CAM_PIN_XCLK GPIO_NUM_NC
|
||||
#define CAM_PIN_SIOD GPIO_NUM_39
|
||||
#define CAM_PIN_SIOC GPIO_NUM_38
|
||||
#define CAM_PIN_D0 GPIO_NUM_4
|
||||
#define CAM_PIN_D1 GPIO_NUM_5
|
||||
#define CAM_PIN_D2 GPIO_NUM_6
|
||||
#define CAM_PIN_D3 GPIO_NUM_7
|
||||
#define CAM_PIN_D4 GPIO_NUM_15
|
||||
#define CAM_PIN_D5 GPIO_NUM_16
|
||||
#define CAM_PIN_D6 GPIO_NUM_17
|
||||
#define CAM_PIN_D7 GPIO_NUM_18
|
||||
#define OV_PWDN_IO 4
|
||||
#define OV_RESET_IO 5
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
#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_I2S_GPIO_MCLK GPIO_NUM_3
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_9
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
|
||||
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_1
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_12
|
||||
#define LCD_MOSI_PIN GPIO_NUM_11
|
||||
#define LCD_MISO_PIN GPIO_NUM_13
|
||||
#define LCD_DC_PIN GPIO_NUM_40
|
||||
#define LCD_CS_PIN GPIO_NUM_21
|
||||
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
/* 相机引脚配置 */
|
||||
#define CAM_PIN_PWDN GPIO_NUM_NC
|
||||
#define CAM_PIN_RESET GPIO_NUM_NC
|
||||
#define CAM_PIN_VSYNC GPIO_NUM_47
|
||||
#define CAM_PIN_HREF GPIO_NUM_48
|
||||
#define CAM_PIN_PCLK GPIO_NUM_45
|
||||
#define CAM_PIN_XCLK GPIO_NUM_NC
|
||||
#define CAM_PIN_SIOD GPIO_NUM_39
|
||||
#define CAM_PIN_SIOC GPIO_NUM_38
|
||||
#define CAM_PIN_D0 GPIO_NUM_4
|
||||
#define CAM_PIN_D1 GPIO_NUM_5
|
||||
#define CAM_PIN_D2 GPIO_NUM_6
|
||||
#define CAM_PIN_D3 GPIO_NUM_7
|
||||
#define CAM_PIN_D4 GPIO_NUM_15
|
||||
#define CAM_PIN_D5 GPIO_NUM_16
|
||||
#define CAM_PIN_D6 GPIO_NUM_17
|
||||
#define CAM_PIN_D7 GPIO_NUM_18
|
||||
#define OV_PWDN_IO 4
|
||||
#define OV_RESET_IO 5
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,220 +1,220 @@
|
||||
#include "ml307_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "led/single_led.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3m_4g"
|
||||
|
||||
class atk_dnesp32s3m_4g : public Ml307Board {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
Button phone_button_;
|
||||
LcdDisplay* display_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
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(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
});
|
||||
|
||||
//不插耳机
|
||||
phone_button_.OnPressDown([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
});
|
||||
|
||||
//插入耳机
|
||||
phone_button_.OnPressUp([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeSt7735Display() {
|
||||
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 = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 60 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7735
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = LCD_RST_PIN;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
|
||||
|
||||
//使能功放引脚
|
||||
gpio_config_t io_conf;
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
|
||||
//检测耳机是否插入,插入时为高电平
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
//耳机插入
|
||||
if (gpio_get_level(PHONE_CK_PIN)) {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
}
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
|
||||
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
|
||||
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
|
||||
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN),
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
|
||||
phone_button_(PHONE_CK_PIN, true) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7735Display();
|
||||
InitializeButtons();
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8388_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3m_4g);
|
||||
#include "ml307_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "led/single_led.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3m_4g"
|
||||
|
||||
class atk_dnesp32s3m_4g : public Ml307Board {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
Button phone_button_;
|
||||
LcdDisplay* display_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
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(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
});
|
||||
|
||||
//不插耳机
|
||||
phone_button_.OnPressDown([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
});
|
||||
|
||||
//插入耳机
|
||||
phone_button_.OnPressUp([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeSt7735Display() {
|
||||
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 = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 60 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7735
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = LCD_RST_PIN;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
|
||||
|
||||
//使能功放引脚
|
||||
gpio_config_t io_conf;
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
|
||||
//检测耳机是否插入,插入时为高电平
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
//耳机插入
|
||||
if (gpio_get_level(PHONE_CK_PIN)) {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
}
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
|
||||
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
|
||||
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
|
||||
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN),
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
|
||||
phone_button_(PHONE_CK_PIN, true) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7735Display();
|
||||
InitializeButtons();
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8388_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3m_4g);
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
|
||||
|
||||
#define Module_4G_RX_PIN GPIO_NUM_21
|
||||
#define Module_4G_TX_PIN GPIO_NUM_45
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
|
||||
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_1
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_12
|
||||
#define LCD_MOSI_PIN GPIO_NUM_11
|
||||
#define LCD_MISO_PIN GPIO_NUM_13
|
||||
#define LCD_DC_PIN GPIO_NUM_40
|
||||
#define LCD_CS_PIN GPIO_NUM_39
|
||||
#define LCD_RST_PIN GPIO_NUM_38
|
||||
|
||||
#define SPK_EN_PIN GPIO_NUM_42
|
||||
#define PHONE_CK_PIN GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 160
|
||||
#define DISPLAY_HEIGHT 80
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 1
|
||||
#define DISPLAY_OFFSET_Y 26
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
|
||||
|
||||
#define Module_4G_RX_PIN GPIO_NUM_21
|
||||
#define Module_4G_TX_PIN GPIO_NUM_45
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
|
||||
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_1
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_12
|
||||
#define LCD_MOSI_PIN GPIO_NUM_11
|
||||
#define LCD_MISO_PIN GPIO_NUM_13
|
||||
#define LCD_DC_PIN GPIO_NUM_40
|
||||
#define LCD_CS_PIN GPIO_NUM_39
|
||||
#define LCD_RST_PIN GPIO_NUM_38
|
||||
|
||||
#define SPK_EN_PIN GPIO_NUM_42
|
||||
#define PHONE_CK_PIN GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 160
|
||||
#define DISPLAY_HEIGHT 80
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 1
|
||||
#define DISPLAY_OFFSET_Y 26
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
@@ -1,230 +1,230 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "led/single_led.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3m_wifi"
|
||||
|
||||
class atk_dnesp32s3m_wifi : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
Button phone_button_;
|
||||
LcdDisplay* display_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
Application::GetInstance().StartListening();
|
||||
});
|
||||
boot_button_.OnPressUp([this]() {
|
||||
Application::GetInstance().StopListening();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
});
|
||||
|
||||
//不插耳机
|
||||
phone_button_.OnPressDown([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
});
|
||||
|
||||
//插入耳机
|
||||
phone_button_.OnPressUp([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void InitializeSt7735Display() {
|
||||
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 = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 60 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = LCD_RST_PIN;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
//使能功放引脚
|
||||
gpio_config_t io_conf;
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
|
||||
//检测耳机是否插入,插入时为高电平
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
//耳机插入
|
||||
if (gpio_get_level(PHONE_CK_PIN)) {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
}
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
|
||||
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
|
||||
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
|
||||
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3m_wifi() :
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
|
||||
phone_button_(PHONE_CK_PIN, true) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7735Display();
|
||||
InitializeButtons();
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8388_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3m_wifi);
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "led/single_led.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <driver/spi_common.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3m_wifi"
|
||||
|
||||
class atk_dnesp32s3m_wifi : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
Button phone_button_;
|
||||
LcdDisplay* display_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)I2C_NUM_0,
|
||||
.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_));
|
||||
}
|
||||
|
||||
// Initialize spi peripheral
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = LCD_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = LCD_SCLK_PIN;
|
||||
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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
Application::GetInstance().StartListening();
|
||||
});
|
||||
boot_button_.OnPressUp([this]() {
|
||||
Application::GetInstance().StopListening();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
GetAudioCodec()->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
|
||||
});
|
||||
|
||||
//不插耳机
|
||||
phone_button_.OnPressDown([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
});
|
||||
|
||||
//插入耳机
|
||||
phone_button_.OnPressUp([this]() {
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void InitializeSt7735Display() {
|
||||
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 = LCD_CS_PIN;
|
||||
io_config.dc_gpio_num = LCD_DC_PIN;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 60 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 7;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = LCD_RST_PIN;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
//使能功放引脚
|
||||
gpio_config_t io_conf;
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(SPK_EN_PIN, 0);
|
||||
|
||||
//检测耳机是否插入,插入时为高电平
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
//耳机插入
|
||||
if (gpio_get_level(PHONE_CK_PIN)) {
|
||||
gpio_set_level(SPK_EN_PIN, 1);
|
||||
}
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
|
||||
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
|
||||
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
|
||||
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
|
||||
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
atk_dnesp32s3m_wifi() :
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
|
||||
phone_button_(PHONE_CK_PIN, true) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7735Display();
|
||||
InitializeButtons();
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_ES8388_ADDR
|
||||
);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3m_wifi);
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
|
||||
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_1
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_12
|
||||
#define LCD_MOSI_PIN GPIO_NUM_11
|
||||
#define LCD_MISO_PIN GPIO_NUM_13
|
||||
#define LCD_DC_PIN GPIO_NUM_40
|
||||
#define LCD_CS_PIN GPIO_NUM_39
|
||||
#define LCD_RST_PIN GPIO_NUM_38
|
||||
|
||||
#define SPK_EN_PIN GPIO_NUM_42
|
||||
#define PHONE_CK_PIN GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 160
|
||||
#define DISPLAY_HEIGHT 80
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 1
|
||||
#define DISPLAY_OFFSET_Y 26
|
||||
// #define DISPLAY_OFFSET_X 0
|
||||
// #define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
|
||||
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_1
|
||||
|
||||
#define LCD_SCLK_PIN GPIO_NUM_12
|
||||
#define LCD_MOSI_PIN GPIO_NUM_11
|
||||
#define LCD_MISO_PIN GPIO_NUM_13
|
||||
#define LCD_DC_PIN GPIO_NUM_40
|
||||
#define LCD_CS_PIN GPIO_NUM_39
|
||||
#define LCD_RST_PIN GPIO_NUM_38
|
||||
|
||||
#define SPK_EN_PIN GPIO_NUM_42
|
||||
#define PHONE_CK_PIN GPIO_NUM_3
|
||||
|
||||
#define DISPLAY_WIDTH 160
|
||||
#define DISPLAY_HEIGHT 80
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 1
|
||||
#define DISPLAY_OFFSET_Y 26
|
||||
// #define DISPLAY_OFFSET_X 0
|
||||
// #define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
# AtomEchoS3R
|
||||
## 简介
|
||||
|
||||
AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
|
||||
|
||||
开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig 并配置**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
分别配置如下选项:
|
||||
|
||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomEchoS3R`
|
||||
- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
|
||||
- `Serial flasher config` → `Flash size` → 选择 `8 MB`
|
||||
- `Component config` → `ESP PSRAM` → `Support for external, SPI-connected RAM` → `SPI RAM config` → 选择 `Octal Mode PSRAM`
|
||||
|
||||
按 `S` 保存,按 `Q` 退出。
|
||||
|
||||
**编译**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**烧录**
|
||||
|
||||
将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
烧录完毕后,按一下 RESET 按钮重启设备。
|
||||
# AtomEchoS3R
|
||||
## 简介
|
||||
|
||||
AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
|
||||
|
||||
开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig 并配置**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
分别配置如下选项:
|
||||
|
||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomEchoS3R`
|
||||
- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
|
||||
- `Serial flasher config` → `Flash size` → 选择 `8 MB`
|
||||
- `Component config` → `ESP PSRAM` → `Support for external, SPI-connected RAM` → `SPI RAM config` → 选择 `Octal Mode PSRAM`
|
||||
|
||||
按 `S` 保存,按 `Q` 退出。
|
||||
|
||||
**编译**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**烧录**
|
||||
|
||||
将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
烧录完毕后,按一下 RESET 按钮重启设备。
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "AtomEchoS3R"
|
||||
|
||||
class AtomEchoS3rBaseBoard : 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_0,
|
||||
.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 I2cDetect() {
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
public:
|
||||
AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AtomEchoS3rBaseBoard);
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "AtomEchoS3R"
|
||||
|
||||
class AtomEchoS3rBaseBoard : 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_0,
|
||||
.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 I2cDetect() {
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
public:
|
||||
AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AtomEchoS3rBaseBoard);
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomEchoS3R Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_3
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define USER_BUTTON_GPIO GPIO_NUM_41
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomEchoS3R Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_3
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define USER_BUTTON_GPIO GPIO_NUM_41
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atom-echos3r",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atom-echos3r",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,37 +1,25 @@
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base
|
||||
```
|
||||
|
||||
**修改 flash 大小:**
|
||||
|
||||
```
|
||||
Serial flasher config -> Flash size -> 4 MB
|
||||
```
|
||||
|
||||
**修改分区表:**
|
||||
|
||||
```
|
||||
Partition Table -> Custom partition CSV file -> partitions/v1/4m.csv
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
@@ -1,133 +1,133 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "led/circular_strip.h"
|
||||
|
||||
#define TAG "XX+EchoBase"
|
||||
|
||||
#define PI4IOE_ADDR 0x43
|
||||
#define PI4IOE_REG_CTRL 0x00
|
||||
#define PI4IOE_REG_IO_PP 0x07
|
||||
#define PI4IOE_REG_IO_DIR 0x03
|
||||
#define PI4IOE_REG_IO_OUT 0x05
|
||||
#define PI4IOE_REG_IO_PULLUP 0x0D
|
||||
|
||||
class Pi4ioe : public I2cDevice {
|
||||
public:
|
||||
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
|
||||
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
|
||||
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
|
||||
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
|
||||
}
|
||||
|
||||
void SetSpeakerMute(bool mute) {
|
||||
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
|
||||
}
|
||||
};
|
||||
|
||||
class AtomMatrixEchoBaseBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
|
||||
Pi4ioe* pi4ioe_;
|
||||
|
||||
Button face_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 I2cDetect() {
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializePi4ioe() {
|
||||
ESP_LOGI(TAG, "Init PI4IOE");
|
||||
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
|
||||
pi4ioe_->SetSpeakerMute(false);
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
face_button_.OnClick([this]() {
|
||||
|
||||
ESP_LOGI(TAG, " ===>>> face_button_.OnClick ");
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
InitializePi4ioe();
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static CircularStrip led(BUILTIN_LED_GPIO, 25);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_1,
|
||||
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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AtomMatrixEchoBaseBoard);
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "led/circular_strip.h"
|
||||
|
||||
#define TAG "XX+EchoBase"
|
||||
|
||||
#define PI4IOE_ADDR 0x43
|
||||
#define PI4IOE_REG_CTRL 0x00
|
||||
#define PI4IOE_REG_IO_PP 0x07
|
||||
#define PI4IOE_REG_IO_DIR 0x03
|
||||
#define PI4IOE_REG_IO_OUT 0x05
|
||||
#define PI4IOE_REG_IO_PULLUP 0x0D
|
||||
|
||||
class Pi4ioe : public I2cDevice {
|
||||
public:
|
||||
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
|
||||
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
|
||||
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
|
||||
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
|
||||
}
|
||||
|
||||
void SetSpeakerMute(bool mute) {
|
||||
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
|
||||
}
|
||||
};
|
||||
|
||||
class AtomMatrixEchoBaseBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
|
||||
Pi4ioe* pi4ioe_;
|
||||
|
||||
Button face_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 I2cDetect() {
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializePi4ioe() {
|
||||
ESP_LOGI(TAG, "Init PI4IOE");
|
||||
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
|
||||
pi4ioe_->SetSpeakerMute(false);
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
face_button_.OnClick([this]() {
|
||||
|
||||
ESP_LOGI(TAG, " ===>>> face_button_.OnClick ");
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
InitializePi4ioe();
|
||||
InitializeButtons();
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static CircularStrip led(BUILTIN_LED_GPIO, 25);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_1,
|
||||
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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AtomMatrixEchoBaseBoard);
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomMatrix+EchoBase Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_19
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_33
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_23
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_22
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_27
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_39
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomMatrix+EchoBase Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_19
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_33
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_23
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_22
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_27
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_39
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
{
|
||||
"target": "esp32",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atommatrix-echo-base",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atommatrix-echo-base",
|
||||
"sdkconfig_append": [
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,49 +1,49 @@
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32S3:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base
|
||||
```
|
||||
|
||||
**关闭语音唤醒:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> [ ] 启用语音唤醒与音频处理 -> Unselect
|
||||
```
|
||||
|
||||
**修改 flash 大小:**
|
||||
|
||||
```
|
||||
Serial flasher config -> Flash size -> 8 MB
|
||||
```
|
||||
|
||||
**修改分区表:**
|
||||
|
||||
```
|
||||
Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
|
||||
```
|
||||
|
||||
**关闭片外 PSRAM:**
|
||||
|
||||
```
|
||||
Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32S3:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base
|
||||
```
|
||||
|
||||
**关闭语音唤醒:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> [ ] 启用语音唤醒与音频处理 -> Unselect
|
||||
```
|
||||
|
||||
**修改 flash 大小:**
|
||||
|
||||
```
|
||||
Serial flasher config -> Flash size -> 8 MB
|
||||
```
|
||||
|
||||
**修改分区表:**
|
||||
|
||||
```
|
||||
Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
|
||||
```
|
||||
|
||||
**关闭片外 PSRAM:**
|
||||
|
||||
```
|
||||
Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
@@ -1,228 +1,228 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_gc9a01.h>
|
||||
|
||||
#define TAG "AtomS3+EchoBase"
|
||||
|
||||
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
|
||||
// {cmd, { data }, data_size, delay_ms}
|
||||
{0xfe, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xef, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xb0, (uint8_t[]){0xc0}, 1, 0},
|
||||
{0xb2, (uint8_t[]){0x2f}, 1, 0},
|
||||
{0xb3, (uint8_t[]){0x03}, 1, 0},
|
||||
{0xb6, (uint8_t[]){0x19}, 1, 0},
|
||||
{0xb7, (uint8_t[]){0x01}, 1, 0},
|
||||
{0xac, (uint8_t[]){0xcb}, 1, 0},
|
||||
{0xab, (uint8_t[]){0x0e}, 1, 0},
|
||||
{0xb4, (uint8_t[]){0x04}, 1, 0},
|
||||
{0xa8, (uint8_t[]){0x19}, 1, 0},
|
||||
{0xb8, (uint8_t[]){0x08}, 1, 0},
|
||||
{0xe8, (uint8_t[]){0x24}, 1, 0},
|
||||
{0xe9, (uint8_t[]){0x48}, 1, 0},
|
||||
{0xea, (uint8_t[]){0x22}, 1, 0},
|
||||
{0xc6, (uint8_t[]){0x30}, 1, 0},
|
||||
{0xc7, (uint8_t[]){0x18}, 1, 0},
|
||||
{0xf0,
|
||||
(uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
|
||||
0x00, 0x1c, 0x1f, 0x0f},
|
||||
14, 0},
|
||||
{0xf1,
|
||||
(uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
|
||||
0x07, 0x0d, 0x11, 0x0f},
|
||||
14, 0},
|
||||
};
|
||||
|
||||
class AtomS3EchoBaseBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Display* display_;
|
||||
Button boot_button_;
|
||||
bool is_echo_base_connected_ = false;
|
||||
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 I2cDetect() {
|
||||
is_echo_base_connected_ = false;
|
||||
uint8_t echo_base_connected_flag = 0x00;
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
if (address == 0x18) {
|
||||
echo_base_connected_flag |= 0xF0;
|
||||
} else if (address == 0x43) {
|
||||
echo_base_connected_flag |= 0x0F;
|
||||
}
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
|
||||
}
|
||||
|
||||
void CheckEchoBaseConnection() {
|
||||
if (is_echo_base_connected_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pop error page
|
||||
InitializeSpi();
|
||||
InitializeGc9107Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->SetBrightness(100);
|
||||
display_->SetStatus(Lang::Strings::ERROR);
|
||||
display_->SetEmotion("triangle_exclamation");
|
||||
display_->SetChatMessage("system", "Echo Base\nnot connected");
|
||||
|
||||
while (1) {
|
||||
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
// Rerun detection
|
||||
I2cDetect();
|
||||
if (is_echo_base_connected_) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
I2cDetect();
|
||||
if (is_echo_base_connected_) {
|
||||
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
ESP_LOGI(TAG, "Initialize SPI bus");
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = GPIO_NUM_21;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = GPIO_NUM_17;
|
||||
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 InitializeGc9107Display() {
|
||||
ESP_LOGI(TAG, "Init GC9107 display");
|
||||
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = GPIO_NUM_15;
|
||||
io_config.dc_gpio_num = GPIO_NUM_33;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 40 * 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, &io_handle));
|
||||
|
||||
ESP_LOGI(TAG, "Install GC9A01 panel driver");
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
gc9a01_vendor_config_t gc9107_vendor_config = {
|
||||
.init_cmds = gc9107_lcd_init_cmds,
|
||||
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
|
||||
};
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use
|
||||
panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
|
||||
panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
|
||||
panel_config.vendor_config = &gc9107_vendor_config;
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||||
|
||||
display_ = new SpiLcdDisplay(io_handle, panel_handle,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
CheckEchoBaseConnection();
|
||||
InitializeSpi();
|
||||
InitializeGc9107Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_1,
|
||||
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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256);
|
||||
return &backlight;
|
||||
}
|
||||
};
|
||||
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_gc9a01.h>
|
||||
|
||||
#define TAG "AtomS3+EchoBase"
|
||||
|
||||
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
|
||||
// {cmd, { data }, data_size, delay_ms}
|
||||
{0xfe, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xef, (uint8_t[]){0x00}, 0, 0},
|
||||
{0xb0, (uint8_t[]){0xc0}, 1, 0},
|
||||
{0xb2, (uint8_t[]){0x2f}, 1, 0},
|
||||
{0xb3, (uint8_t[]){0x03}, 1, 0},
|
||||
{0xb6, (uint8_t[]){0x19}, 1, 0},
|
||||
{0xb7, (uint8_t[]){0x01}, 1, 0},
|
||||
{0xac, (uint8_t[]){0xcb}, 1, 0},
|
||||
{0xab, (uint8_t[]){0x0e}, 1, 0},
|
||||
{0xb4, (uint8_t[]){0x04}, 1, 0},
|
||||
{0xa8, (uint8_t[]){0x19}, 1, 0},
|
||||
{0xb8, (uint8_t[]){0x08}, 1, 0},
|
||||
{0xe8, (uint8_t[]){0x24}, 1, 0},
|
||||
{0xe9, (uint8_t[]){0x48}, 1, 0},
|
||||
{0xea, (uint8_t[]){0x22}, 1, 0},
|
||||
{0xc6, (uint8_t[]){0x30}, 1, 0},
|
||||
{0xc7, (uint8_t[]){0x18}, 1, 0},
|
||||
{0xf0,
|
||||
(uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
|
||||
0x00, 0x1c, 0x1f, 0x0f},
|
||||
14, 0},
|
||||
{0xf1,
|
||||
(uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
|
||||
0x07, 0x0d, 0x11, 0x0f},
|
||||
14, 0},
|
||||
};
|
||||
|
||||
class AtomS3EchoBaseBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Display* display_;
|
||||
Button boot_button_;
|
||||
bool is_echo_base_connected_ = false;
|
||||
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 I2cDetect() {
|
||||
is_echo_base_connected_ = false;
|
||||
uint8_t echo_base_connected_flag = 0x00;
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
if (address == 0x18) {
|
||||
echo_base_connected_flag |= 0xF0;
|
||||
} else if (address == 0x43) {
|
||||
echo_base_connected_flag |= 0x0F;
|
||||
}
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
|
||||
}
|
||||
|
||||
void CheckEchoBaseConnection() {
|
||||
if (is_echo_base_connected_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pop error page
|
||||
InitializeSpi();
|
||||
InitializeGc9107Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->SetBrightness(100);
|
||||
display_->SetStatus(Lang::Strings::ERROR);
|
||||
display_->SetEmotion("triangle_exclamation");
|
||||
display_->SetChatMessage("system", "Echo Base\nnot connected");
|
||||
|
||||
while (1) {
|
||||
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
// Rerun detection
|
||||
I2cDetect();
|
||||
if (is_echo_base_connected_) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
I2cDetect();
|
||||
if (is_echo_base_connected_) {
|
||||
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
ESP_LOGI(TAG, "Initialize SPI bus");
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = GPIO_NUM_21;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = GPIO_NUM_17;
|
||||
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 InitializeGc9107Display() {
|
||||
ESP_LOGI(TAG, "Init GC9107 display");
|
||||
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = GPIO_NUM_15;
|
||||
io_config.dc_gpio_num = GPIO_NUM_33;
|
||||
io_config.spi_mode = 0;
|
||||
io_config.pclk_hz = 40 * 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, &io_handle));
|
||||
|
||||
ESP_LOGI(TAG, "Install GC9A01 panel driver");
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
gc9a01_vendor_config_t gc9107_vendor_config = {
|
||||
.init_cmds = gc9107_lcd_init_cmds,
|
||||
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
|
||||
};
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use
|
||||
panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
|
||||
panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
|
||||
panel_config.vendor_config = &gc9107_vendor_config;
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||||
|
||||
display_ = new SpiLcdDisplay(io_handle, panel_handle,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
CheckEchoBaseConnection();
|
||||
InitializeSpi();
|
||||
InitializeGc9107Display();
|
||||
InitializeButtons();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
I2C_NUM_1,
|
||||
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_GPIO_PA,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
false);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256);
|
||||
return &backlight;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AtomS3EchoBaseBoard);
|
||||
@@ -1,43 +1,43 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomS3+EchoBase Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_41
|
||||
#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 128
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 32
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// AtomS3+EchoBase Board configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
|
||||
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_41
|
||||
#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 128
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY false
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 32
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atoms3-echo-base",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_SPIRAM=n",
|
||||
"CONFIG_USE_AFE=n",
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atoms3-echo-base",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_SPIRAM=n",
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,54 +1,54 @@
|
||||
# AtomS3R CAM/M12 + Echo Base
|
||||
|
||||
## 简介
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam"><b> AtomS3R CAM 产品主页 </b></a>
|
||||
|
|
||||
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R-M12"><b> AtomS3R M12 产品主页 </b></a>
|
||||
|
|
||||
<a href="https://docs.m5stack.com/zh_CN/atom/Atomic%20Echo%20Base"><b> Echo Base 产品主页 </b></a>
|
||||
</div>
|
||||
|
||||
AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
|
||||
|
||||
两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig 并配置**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
分别配置如下选项:
|
||||
|
||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base`
|
||||
- `Xiaozhi Assistant` → `IoT Protocol` → 选择 `MCP协议` 可开启摄像头识别功能
|
||||
- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
|
||||
- `Serial flasher config` → `Flash size` → 选择 `8 MB`
|
||||
|
||||
按 `S` 保存,按 `Q` 退出。
|
||||
|
||||
**编译**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**烧录**
|
||||
|
||||
将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
烧录完毕后,按一下 RESET 按钮重启。
|
||||
# AtomS3R CAM/M12 + Echo Base
|
||||
|
||||
## 简介
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam"><b> AtomS3R CAM 产品主页 </b></a>
|
||||
|
|
||||
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R-M12"><b> AtomS3R M12 产品主页 </b></a>
|
||||
|
|
||||
<a href="https://docs.m5stack.com/zh_CN/atom/Atomic%20Echo%20Base"><b> Echo Base 产品主页 </b></a>
|
||||
</div>
|
||||
|
||||
AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
|
||||
|
||||
两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig 并配置**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
分别配置如下选项:
|
||||
|
||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base`
|
||||
- `Xiaozhi Assistant` → `IoT Protocol` → 选择 `MCP协议` 可开启摄像头识别功能
|
||||
- `Partition Table` → `Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
|
||||
- `Serial flasher config` → `Flash size` → 选择 `8 MB`
|
||||
|
||||
按 `S` 保存,按 `Q` 退出。
|
||||
|
||||
**编译**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**烧录**
|
||||
|
||||
将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
烧录完毕后,按一下 RESET 按钮重启。
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user