1. 完成了语音识别的C++业务层封装,测试通过
2. 试着测试了一下LVGL_GIF渲染+音乐播放+语音识别的组合简单优化后,
发现lvgl渲染略显卡顿,语音识别有缓冲区空警告,不过无伤大雅,还需要进一步深度优化。
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/22.
|
||||
//
|
||||
#include "cpp_json.h"
|
||||
@@ -0,0 +1,207 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/22.
|
||||
//
|
||||
/**
|
||||
* CppJson.h
|
||||
* 对cJSON的Cpp封装
|
||||
* Code By Misaki
|
||||
* 2025.9.22
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如何使用:
|
||||
std::string text = R"({"name":"Misaki","age":18,"skills":["C++","RISC-V"]})";
|
||||
auto j = cppjson::Json::parse(text);
|
||||
std::cout << "name = " << j["name"].asString() << '\n';
|
||||
j["skills"].append(cppjson::Json("Go"));
|
||||
std::cout << j.dumpPretty() << '\n';
|
||||
|
||||
or:
|
||||
Json j = Json::object();
|
||||
j.set("name", Json("misaki"))
|
||||
.set("age", Json(24))
|
||||
.set("vip", Json(true))
|
||||
.set("list", Json::array()
|
||||
.append(Json(1))
|
||||
.append(Json("hello")));
|
||||
|
||||
std::cout << j.dumpPretty() << "\n";
|
||||
|
||||
Json j2 = j["list"];
|
||||
for (auto& v : j2) std::cout << v.dump() << " ";
|
||||
std::cout << "\n";
|
||||
*/
|
||||
|
||||
// cpp_json.hpp
|
||||
#pragma once
|
||||
#include <cJSON.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cppjson {
|
||||
|
||||
//前向声明 + 别名
|
||||
class Json;
|
||||
using Array = std::vector<Json>; // 这里只是别名,不会实例化
|
||||
using Object = std::map<std::string, Json>;
|
||||
|
||||
|
||||
class Json {
|
||||
public:
|
||||
enum Type { Null, Bool, Number, String, ArrayType, ObjectType };
|
||||
|
||||
/*-------- 构造 ------------------------------------------------*/
|
||||
Json() noexcept : ptr_(nullptr), owner_(false) {}
|
||||
explicit Json(std::nullptr_t) noexcept : Json() {}
|
||||
explicit Json(bool v) : ptr_(cJSON_CreateBool(v)), owner_(true) {}
|
||||
explicit Json(int v) : ptr_(cJSON_CreateNumber(v)), owner_(true) {}
|
||||
explicit Json(double v) : ptr_(cJSON_CreateNumber(v)), owner_(true) {}
|
||||
explicit Json(const char* v) : ptr_(cJSON_CreateString(v)), owner_(true) {}
|
||||
explicit Json(const std::string& v):ptr_(cJSON_CreateString(v.c_str())), owner_(true) {}
|
||||
explicit Json(const Array& arr); // 类外实现
|
||||
explicit Json(const Object& obj); // 类外实现
|
||||
/* 接管原始指针,owner=true 会负责 delete */
|
||||
explicit Json(cJSON* raw, bool owner = true) noexcept
|
||||
: ptr_(raw), owner_(owner) {}
|
||||
|
||||
/*-------- 拷贝/移动 -------------------------------------------*/
|
||||
Json(const Json& rhs)
|
||||
: ptr_(rhs.ptr_ ? cJSON_Duplicate(rhs.ptr_, 1) : nullptr), owner_(true) {}
|
||||
Json(Json&& rhs) noexcept
|
||||
: ptr_(rhs.ptr_), owner_(rhs.owner_) { rhs.ptr_ = nullptr; rhs.owner_ = false; }
|
||||
Json& operator=(Json rhs) noexcept { swap(rhs); return *this; }
|
||||
~Json() { reset(); }
|
||||
|
||||
/*-------- 工厂 ------------------------------------------------*/
|
||||
static Json parse(const std::string& text) {
|
||||
cJSON* p = cJSON_Parse(text.c_str());
|
||||
if (!p) throw std::runtime_error("cppjson::parse failed");
|
||||
return Json(p, true);
|
||||
}
|
||||
static Json array() { return Json(cJSON_CreateArray(), true); }
|
||||
static Json object() { return Json(cJSON_CreateObject(), true); }
|
||||
|
||||
/*-------- 序列化 ----------------------------------------------*/
|
||||
[[nodiscard]] std::string dump() const {
|
||||
if (!ptr_) return "null";
|
||||
char* s = cJSON_PrintUnformatted(ptr_);
|
||||
std::string ret(s ? s : "null");
|
||||
if (s) free(s);
|
||||
return ret;
|
||||
}
|
||||
[[nodiscard]] std::string dumpPretty() const {
|
||||
if (!ptr_) return "null";
|
||||
char* s = cJSON_Print(ptr_);
|
||||
std::string ret(s ? s : "null");
|
||||
if (s) free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*-------- 类型查询 --------------------------------------------*/
|
||||
[[nodiscard]] Type type() const noexcept {
|
||||
if (!ptr_) return Null;
|
||||
switch (ptr_->type & 0xFF) {
|
||||
case cJSON_False:
|
||||
case cJSON_True: return Bool;
|
||||
case cJSON_Number:return Number;
|
||||
case cJSON_String:return String;
|
||||
case cJSON_Array: return ArrayType;
|
||||
case cJSON_Object:return ObjectType;
|
||||
default: return Null;
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool isNull() const noexcept { return type() == Null; }
|
||||
[[nodiscard]] bool isBool() const noexcept { return type() == Bool; }
|
||||
[[nodiscard]] bool isNumber() const noexcept { return type() == Number; }
|
||||
[[nodiscard]] bool isString() const noexcept { return type() == String; }
|
||||
[[nodiscard]] bool isArray() const noexcept { return type() == ArrayType; }
|
||||
[[nodiscard]] bool isObject() const noexcept { return type() == ObjectType; }
|
||||
|
||||
/*-------- 取值 ------------------------------------------------*/
|
||||
[[nodiscard]] bool asBool() const { if (!isBool()) throw std::runtime_error("not bool"); return cJSON_IsTrue(ptr_); }
|
||||
[[nodiscard]] int asInt() const { if (!isNumber()) throw std::runtime_error("not number"); return static_cast<int>(ptr_->valueint); }
|
||||
[[nodiscard]] double asDouble() const { if (!isNumber()) throw std::runtime_error("not number"); return ptr_->valuedouble; }
|
||||
[[nodiscard]] std::string asString() const { if (!isString()) throw std::runtime_error("not string"); return ptr_->valuestring; }
|
||||
|
||||
/*-------- 数组/对象接口 ---------------------------------------*/
|
||||
[[nodiscard]] size_t size() const {
|
||||
if (isArray() || isObject()) return cJSON_GetArraySize(ptr_);
|
||||
return 0;
|
||||
}
|
||||
Json operator[](size_t idx) const {
|
||||
if (!isArray()) throw std::runtime_error("not array");
|
||||
cJSON* it = cJSON_GetArrayItem(ptr_, static_cast<int>(idx));
|
||||
return it ? Json(it, false) : Json();
|
||||
}
|
||||
Json operator[](const std::string& key) const {
|
||||
if (!isObject()) throw std::runtime_error("not object");
|
||||
cJSON* it = cJSON_GetObjectItem(ptr_, key.c_str());
|
||||
return it ? Json(it, false) : Json();
|
||||
}
|
||||
Json& append(const Json& v) {
|
||||
if (!isArray()) throw std::runtime_error("not array");
|
||||
cJSON_AddItemToArray(ptr_, cJSON_Duplicate(v.ptr_, 1));
|
||||
return *this;
|
||||
}
|
||||
Json& set(const std::string& key, const Json& v) {
|
||||
if (!isObject()) throw std::runtime_error("not object");
|
||||
cJSON_AddItemToObject(ptr_, key.c_str(), cJSON_Duplicate(v.ptr_, 1));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*-------- 迭代器(只读)---------------------------------------*/
|
||||
template <bool IsConst>
|
||||
struct iterator_impl {
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = Json;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = typename std::conditional<IsConst, const Json, Json>::type*;
|
||||
using reference = typename std::conditional<IsConst, const Json, Json>::type&;
|
||||
|
||||
iterator_impl(cJSON* h, cJSON* c) : head_(h), cur_(c) {}
|
||||
reference operator*() {
|
||||
tmp = std::make_unique<Json>(cur_, false);
|
||||
return *tmp;
|
||||
}
|
||||
pointer operator->() { return &(operator*()); }
|
||||
iterator_impl& operator++() { cur_ = cur_ ? cur_->next : nullptr; return *this; }
|
||||
friend bool operator==(const iterator_impl& a, const iterator_impl& b) { return a.cur_ == b.cur_; }
|
||||
friend bool operator!=(const iterator_impl& a, const iterator_impl& b) { return !(a == b); }
|
||||
private:
|
||||
cJSON *head_, *cur_;
|
||||
std::unique_ptr<Json> tmp; // 改为智能指针
|
||||
};
|
||||
using iterator = iterator_impl<false>;
|
||||
using const_iterator = iterator_impl<true>;
|
||||
iterator begin() { return iterator(ptr_, ptr_ ? ptr_->child : nullptr); }
|
||||
iterator end() { return iterator(ptr_, nullptr); }
|
||||
[[nodiscard]] const_iterator begin() const { return cbegin(); }
|
||||
[[nodiscard]] const_iterator end() const { return cend(); }
|
||||
[[nodiscard]] const_iterator cbegin() const { return const_iterator(ptr_, ptr_ ? ptr_->child : nullptr); }
|
||||
[[nodiscard]] const_iterator cend() const { return const_iterator(ptr_, nullptr); }
|
||||
|
||||
/*-------- 工具 ------------------------------------------------*/
|
||||
void swap(Json& rhs) noexcept { std::swap(ptr_, rhs.ptr_); std::swap(owner_, rhs.owner_); }
|
||||
|
||||
private:
|
||||
cJSON* ptr_ = nullptr;
|
||||
bool owner_ = false;
|
||||
|
||||
void reset() { if (owner_ && ptr_) cJSON_Delete(ptr_); ptr_ = nullptr; owner_ = true; }
|
||||
};
|
||||
|
||||
|
||||
/*================ 类外实现:Array / Object 构造 =================*/
|
||||
inline Json::Json(const Array& arr)
|
||||
: ptr_(cJSON_CreateArray()), owner_(true) {
|
||||
for (const auto& v : arr) append(v);
|
||||
}
|
||||
inline Json::Json(const Object& obj)
|
||||
: ptr_(cJSON_CreateObject()), owner_(true) {
|
||||
for (const auto& kv : obj) set(kv.first, kv.second);
|
||||
}
|
||||
|
||||
} // namespace cppjson
|
||||
@@ -16,6 +16,9 @@
|
||||
#include <core/lv_obj.h>
|
||||
|
||||
class LVGLRender {
|
||||
public:
|
||||
LVGLRender(LVGLRender const&) = delete; // 禁止拷贝构造函数
|
||||
LVGLRender& operator=(LVGLRender const&) = delete;// 禁止赋值运算符
|
||||
public:
|
||||
static std::string makeFullPath(const std::string& filename);
|
||||
|
||||
@@ -46,10 +49,6 @@ private:
|
||||
private:
|
||||
explicit LVGLRender(); // 构造函数私有化
|
||||
~LVGLRender();
|
||||
|
||||
LVGLRender(LVGLRender const&) = delete; // 拷贝构造函数私有化
|
||||
LVGLRender& operator=(LVGLRender const&) = delete;// 赋值运算符私有化
|
||||
|
||||
void LVGL_Update(); // 渲染lvgl上下文(持久性线程)
|
||||
|
||||
private:
|
||||
|
||||
@@ -46,8 +46,8 @@ struct SpeechRecognizerConfig {
|
||||
// 模型路径
|
||||
std::string model_path = "/sdcard/srmodels";
|
||||
// 线程配置
|
||||
ThreadConfig feed_thread_config = {"SR_Feed", 0, 4096, 3, false};
|
||||
ThreadConfig detect_thread_config = {"SR_Detect", 1, 6 * 1024, 5, false};
|
||||
ThreadConfig feed_thread_config = {"SR_Feed", 1, 4096, 3, false};
|
||||
ThreadConfig detect_thread_config = {"SR_Detect", 0, 6 * 1024, 5, false};
|
||||
// 识别超时时间(ms)
|
||||
int detection_timeout = 6000;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/22.
|
||||
//
|
||||
|
||||
#include "sys_conf_singleton.h"
|
||||
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/22.
|
||||
//
|
||||
#pragma once
|
||||
#include "cpp_json.h"
|
||||
#include <esp_vfs_fat.h>
|
||||
#include <wear_levelling.h>
|
||||
#include <esp_log.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
|
||||
static constexpr char kSysConfPartName[] = "sys_conf";
|
||||
|
||||
template <const char* PartitionLabel>
|
||||
class SysConfJson {
|
||||
public:
|
||||
static SysConfJson& instance() {
|
||||
static SysConfJson inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
/* 把 sn 持久化到 <mount>/sn.json,成功返回 true */
|
||||
bool saveSN(const std::string& sn)
|
||||
{
|
||||
cppjson::Json doc = cppjson::Json::object();
|
||||
doc.set("sn", cppjson::Json(sn)); // {"sn":"xxxxxx"}
|
||||
return write("sn", doc); // 实际文件 = <mount>/sn.json
|
||||
}
|
||||
|
||||
/* 读取 sn,如果文件/字段不存在返回空串(绝不抛异常) */
|
||||
std::string loadSN()
|
||||
{
|
||||
cppjson::Json j = read("sn"); // 读 <mount>/sn.json
|
||||
if (!j.isObject()) return ""; // 文件不存在或解析失败
|
||||
cppjson::Json snNode = j["sn"];
|
||||
return snNode.isString() ? snNode.asString() : "";
|
||||
}
|
||||
|
||||
/* 直接覆盖写,无 tmp */
|
||||
bool write(const char* key, const cppjson::Json& j) {
|
||||
const std::string path = buildPath(key);
|
||||
const std::string txt = j.dump();
|
||||
return writeRaw(path, txt);
|
||||
}
|
||||
|
||||
/* 读 */
|
||||
cppjson::Json read(const char* key) const {
|
||||
std::string content;
|
||||
if (!readRaw(key, content)) return {};
|
||||
try {
|
||||
return cppjson::Json::parse(content);
|
||||
} catch (...) {
|
||||
ESP_LOGE(TAG, "parse %s fail", key);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/* 删文件 */
|
||||
bool remove(const char* key) const {
|
||||
const std::string path = buildPath(key);
|
||||
return ::unlink(path.c_str()) == 0;
|
||||
}
|
||||
|
||||
/* 列文件 */
|
||||
[[nodiscard]] std::vector<std::string> ls() const {
|
||||
std::vector<std::string> names;
|
||||
DIR* dir = ::opendir(kMount);
|
||||
if (!dir) return names;
|
||||
struct dirent* entry;
|
||||
while ((entry = ::readdir(dir)) != nullptr) {
|
||||
if (entry->d_name[0] == '.') continue;
|
||||
names.emplace_back(entry->d_name);
|
||||
}
|
||||
::closedir(dir);
|
||||
return names;
|
||||
}
|
||||
|
||||
/* 格式化 */
|
||||
[[nodiscard]] bool format() const {
|
||||
ESP_LOGW(TAG, "format <%s>", PartitionLabel);
|
||||
return esp_vfs_fat_spiflash_format_rw_wl(kMount, PartitionLabel) == ESP_OK;
|
||||
}
|
||||
|
||||
/* 禁止拷贝 */
|
||||
SysConfJson(const SysConfJson&) = delete;
|
||||
SysConfJson& operator=(const SysConfJson&) = delete;
|
||||
|
||||
private:
|
||||
constexpr static const char* TAG = "SysConfJson";
|
||||
constexpr static const char* kMount = "/sys_conf";
|
||||
wl_handle_t wl_ = WL_INVALID_HANDLE;
|
||||
bool mounted_ = false;
|
||||
static constexpr const char* kFileName = "sys_conf.json";
|
||||
std::mutex mtx_;
|
||||
|
||||
SysConfJson() { mount(); }
|
||||
~SysConfJson() { unmount(); }
|
||||
|
||||
void mount() {
|
||||
esp_vfs_fat_mount_config_t cfg = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 8,
|
||||
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE
|
||||
};
|
||||
esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(kMount, PartitionLabel, &cfg, &wl_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mount fail <%s>", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
mounted_ = true;
|
||||
ESP_LOGI(TAG, "FAT mounted at %s", kMount);
|
||||
}
|
||||
|
||||
void unmount() {
|
||||
if (!mounted_) return;
|
||||
esp_vfs_fat_spiflash_unmount_rw_wl(kMount, wl_);
|
||||
wl_ = WL_INVALID_HANDLE;
|
||||
mounted_ = false;
|
||||
}
|
||||
|
||||
bool writeRaw(const std::string& path, const std::string& txt) {
|
||||
FILE* f = std::fopen(path.c_str(), "wb");
|
||||
if (!f) return false;
|
||||
bool ok = std::fwrite(txt.data(), 1, txt.size(), f) == txt.size();
|
||||
std::fclose(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool readRaw(const char* key, std::string& out) const {
|
||||
const std::string path = buildPath(key);
|
||||
FILE* f = std::fopen(path.c_str(), "rb");
|
||||
if (!f) return false;
|
||||
std::fseek(f, 0, SEEK_END);
|
||||
size_t sz = std::ftell(f);
|
||||
std::fseek(f, 0, SEEK_SET);
|
||||
out.resize(sz);
|
||||
bool ok = std::fread(&out[0], 1, sz, f) == sz;
|
||||
std::fclose(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::string buildPath(const char* key) const {
|
||||
return std::string(kMount) + "/" + key;
|
||||
}
|
||||
};
|
||||
|
||||
#define SYS_CONF_JSON() SysConfJson<kSysConfPartName>::instance()
|
||||
@@ -142,8 +142,6 @@ public:
|
||||
printf("Internal(内部): %zu kB, SPIRAM(外部): %zu kB\n", internal / 1024, spiram / 1024);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void stats_task()
|
||||
{
|
||||
char stats_buf[2 * 1024]; // 存储任务列表和绑核信息,占用 2KB 栈空间,调用时需注意
|
||||
|
||||
@@ -1,4 +1,42 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/2.
|
||||
//
|
||||
#include "ToolsClass.h"
|
||||
|
||||
#include <esp_mac.h>
|
||||
#include <esp_efuse.h>
|
||||
#include <esp_log.h>
|
||||
#include <array>
|
||||
#include <esp_efuse_table.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
static const char *TAG = "ToolsClass";
|
||||
|
||||
|
||||
std::string ToolsClass::getChipSerialNumber() {
|
||||
// 获取芯片序列号(17 字节长的 "Chip ID" (乐鑫官方唯一序列号))
|
||||
std::array<uint8_t, 17> id{};
|
||||
/* 下面这个宏在 ESP32-S3 上展开为 17 byte 序列号字段,直接可用 */
|
||||
esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, id.data(), id.size() * 8);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "read Chip ID failed: %s", esp_err_to_name(err));
|
||||
return "";
|
||||
}
|
||||
std::stringstream ss;
|
||||
for (uint8_t v : id) ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(v);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ToolsClass::getChipMAC() {
|
||||
std::array<uint8_t, 6> mac{};
|
||||
esp_efuse_mac_get_default(mac.data()); // 读出厂 MAC
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < mac.size(); ++i) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(mac[i]);
|
||||
if (i + 1 != mac.size()) ss << ':';
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,11 +2,30 @@
|
||||
// Created by misaki on 2025/9/2.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
/**
|
||||
* 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装
|
||||
*
|
||||
*
|
||||
*/
|
||||
class ToolsClass {
|
||||
public:
|
||||
/**
|
||||
* 获取当前时间
|
||||
* @return
|
||||
*/
|
||||
static std::string getCurrentTime();
|
||||
|
||||
/**
|
||||
* 获取esp32s3的芯片序列号
|
||||
* @return
|
||||
*/
|
||||
static std::string getChipSerialNumber();
|
||||
|
||||
/**
|
||||
* 获取esp32s3的MAC地址
|
||||
* @return
|
||||
*/
|
||||
static std::string getChipMAC();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user