add iot framework

This commit is contained in:
Terrence
2024-12-06 11:08:49 +08:00
parent 43b1046df5
commit d31901e9e5
27 changed files with 686 additions and 76 deletions

View File

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

View File

@@ -9,6 +9,8 @@ set(SOURCES "audio_codecs/audio_codec.cc"
"protocols/protocol.cc"
"protocols/mqtt_protocol.cc"
"protocols/websocket_protocol.cc"
"iot/thing.cc"
"iot/thing_manager.cc"
"system_info.cc"
"application.cc"
"ota.cc"
@@ -19,6 +21,10 @@ set(SOURCES "audio_codecs/audio_codec.cc"
set(INCLUDE_DIRS "." "display" "audio_codecs" "protocols" "audio_processing")
# 添加 IOT 相关文件
file(GLOB IOT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/iot/things/*.cc)
list(APPEND SOURCES ${IOT_SOURCES})
# 字体
file(GLOB FONT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/fonts/*.c)
list(APPEND SOURCES ${FONT_SOURCES})
@@ -44,6 +50,8 @@ elseif(CONFIG_BOARD_TYPE_KEVIN_C3)
set(BOARD_TYPE "kevin-c3")
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV)
set(BOARD_TYPE "lichuang-dev")
elseif(CONFIG_BOARD_TYPE_TERRENCE_C3_DEV)
set(BOARD_TYPE "terrence-c3-dev")
endif()
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES})
@@ -55,6 +63,7 @@ endif()
idf_component_register(SRCS ${SOURCES}
EMBED_FILES "assets/err_reg.p3" "assets/err_pin.p3" "assets/err_wificonfig.p3"
INCLUDE_DIRS ${INCLUDE_DIRS}
WHOLE_ARCHIVE
)
# 使用 target_compile_definitions 来定义 BOARD_TYPE

View File

@@ -1,10 +1,13 @@
#include "application.h"
#include "board.h"
#include "display.h"
#include "system_info.h"
#include "ml307_ssl_transport.h"
#include "audio_codec.h"
#include "mqtt_protocol.h"
#include "websocket_protocol.h"
#include "font_awesome_symbols.h"
#include "iot/thing_manager.h"
#include <cstring>
#include <esp_log.h>
@@ -183,8 +186,6 @@ void Application::StopListening() {
void Application::Start() {
auto& board = Board::GetInstance();
board.Initialize();
auto builtin_led = board.GetBuiltinLed();
builtin_led->SetBlue();
builtin_led->StartContinuousBlink(100);
@@ -308,18 +309,22 @@ void Application::Start() {
}
});
protocol_->OnAudioChannelOpened([this, codec, &board]() {
board.SetPowerSaveMode(false);
if (protocol_->server_sample_rate() != codec->output_sample_rate()) {
ESP_LOGW(TAG, "服务器的音频采样率 %d 与设备输出的采样率 %d 不一致,重采样后可能会失真",
protocol_->server_sample_rate(), codec->output_sample_rate());
}
SetDecodeSampleRate(protocol_->server_sample_rate());
board.SetPowerSaveMode(false);
// 物联网设备描述符
last_iot_states_.clear();
auto& thing_manager = iot::ThingManager::GetInstance();
protocol_->SendIotDescriptors(thing_manager.GetDescriptorsJson());
});
protocol_->OnAudioChannelClosed([this, &board]() {
board.SetPowerSaveMode(true);
Schedule([this]() {
SetChatState(kChatStateIdle);
});
board.SetPowerSaveMode(true);
});
protocol_->OnIncomingJson([this, display](const cJSON* root) {
// Parse JSON data
@@ -363,6 +368,15 @@ void Application::Start() {
if (emotion != NULL) {
display->SetEmotion(emotion->valuestring);
}
} else if (strcmp(type->valuestring, "iot") == 0) {
auto commands = cJSON_GetObjectItem(root, "commands");
if (commands != NULL) {
auto& thing_manager = iot::ThingManager::GetInstance();
for (int i = 0; i < cJSON_GetArraySize(commands); ++i) {
auto command = cJSON_GetArrayItem(commands, i);
thing_manager.Invoke(command);
}
}
}
});
@@ -557,6 +571,7 @@ void Application::SetChatState(ChatState state) {
#if CONFIG_IDF_TARGET_ESP32S3
audio_processor_.Start();
#endif
UpdateIotStates();
break;
case kChatStateSpeaking:
builtin_led->SetGreen();
@@ -591,3 +606,12 @@ void Application::SetDecodeSampleRate(int sample_rate) {
output_resampler_.Configure(opus_decode_sample_rate_, codec->output_sample_rate());
}
}
void Application::UpdateIotStates() {
auto& thing_manager = iot::ThingManager::GetInstance();
auto states = thing_manager.GetStatesJson();
if (states != last_iot_states_) {
last_iot_states_ = states;
protocol_->SendIotStates(states);
}
}

View File

@@ -4,18 +4,16 @@
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#include <opus.h>
#include <string>
#include <mutex>
#include <list>
#include <condition_variable>
#include "opus_encoder.h"
#include "opus_decoder.h"
#include "opus_resampler.h"
#include <opus_encoder.h>
#include <opus_decoder.h>
#include <opus_resampler.h>
#include "protocol.h"
#include "display.h"
#include "board.h"
#include "ota.h"
#include "background_task.h"
@@ -58,6 +56,7 @@ public:
void ToggleChatState();
void StartListening();
void StopListening();
void UpdateIotStates();
private:
Application();
@@ -75,6 +74,7 @@ private:
volatile ChatState chat_state_ = kChatStateUnknown;
bool keep_listening_ = false;
bool aborted_ = false;
std::string last_iot_states_;
// Audio encode / decode
BackgroundTask background_task_;

View File

@@ -6,6 +6,7 @@
#include "button.h"
#include "led.h"
#include "config.h"
#include "iot/thing_manager.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
@@ -59,8 +60,7 @@ private:
});
volume_up_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(100);
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification("最大音量");
});
@@ -75,12 +75,18 @@ private:
});
volume_down_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(0);
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification("已静音");
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Lamp"));
}
public:
CompactMl307Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096),
boot_button_(BOOT_BUTTON_GPIO),
@@ -88,17 +94,12 @@ public:
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
system_reset_(RESET_NVS_BUTTON_GPIO, RESET_FACTORY_BUTTON_GPIO) {
}
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing CompactMl307Board");
// Check if the reset button is pressed
system_reset_.CheckButtons();
InitializeDisplayI2c();
InitializeButtons();
Ml307Board::Initialize();
InitializeIot();
}
virtual Led* GetBuiltinLed() override {

View File

@@ -6,6 +6,7 @@
#include "button.h"
#include "led.h"
#include "config.h"
#include "iot/thing_manager.h"
#include <wifi_station.h>
#include <esp_log.h>
@@ -64,8 +65,7 @@ private:
});
volume_up_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(100);
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification("最大音量");
});
@@ -80,30 +80,31 @@ private:
});
volume_down_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(0);
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification("已静音");
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Lamp"));
}
public:
CompactWifiBoard() :
boot_button_(BOOT_BUTTON_GPIO),
touch_button_(TOUCH_BUTTON_GPIO, 1),
touch_button_(TOUCH_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
system_reset_(RESET_NVS_BUTTON_GPIO, RESET_FACTORY_BUTTON_GPIO) {
}
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing CompactWifiBoard");
// Check if the reset button is pressed
system_reset_.CheckButtons();
InitializeDisplayI2c();
InitializeButtons();
WifiBoard::Initialize();
InitializeIot();
}
virtual Led* GetBuiltinLed() override {

View File

@@ -6,7 +6,10 @@
#include <esp_ota_ops.h>
#include <esp_chip_info.h>
// static const char *TAG = "Board";
#define TAG "Board"
Board::Board() {
}
bool Board::GetBatteryLevel(int &level, bool& charging) {
return false;

View File

@@ -19,7 +19,7 @@ private:
virtual std::string GetBoardJson() = 0;
protected:
Board() = default;
Board();
public:
static Board& GetInstance() {
@@ -30,7 +30,6 @@ public:
return *instance;
}
virtual void Initialize() = 0;
virtual void StartNetwork() = 0;
virtual ~Board() = default;
virtual Led* GetBuiltinLed() = 0;

View File

@@ -1,5 +1,7 @@
#include "ml307_board.h"
#include "application.h"
#include "display.h"
#include "font_awesome_symbols.h"
#include <esp_log.h>
@@ -72,10 +74,9 @@ void Ml307Board::WaitForNetworkReady() {
ESP_LOGI(TAG, "ML307 Module: %s", module_name.c_str());
ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str());
ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str());
}
void Ml307Board::Initialize() {
ESP_LOGI(TAG, "Initializing Ml307Board");
// Close all previous connections
modem_.ResetConnections();
}
Http* Ml307Board::CreateHttp() {

View File

@@ -13,7 +13,6 @@ protected:
public:
Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size = 4096);
virtual void Initialize() override;
virtual void StartNetwork() override;
virtual Http* CreateHttp() override;
virtual WebSocket* CreateWebSocket() override;

View File

@@ -1,4 +1,6 @@
#include "wifi_board.h"
#include "display.h"
#include "application.h"
#include "system_info.h"
#include "font_awesome_symbols.h"
@@ -70,10 +72,6 @@ void WifiBoard::StartNetwork() {
}
}
void WifiBoard::Initialize() {
ESP_LOGI(TAG, "Initializing WifiBoard");
}
Http* WifiBoard::CreateHttp() {
return new EspHttp();
}

View File

@@ -10,7 +10,6 @@ protected:
virtual std::string GetBoardJson() override;
public:
virtual void Initialize() override;
virtual void StartNetwork() override;
virtual Http* CreateHttp() override;
virtual WebSocket* CreateWebSocket() override;

View File

@@ -41,13 +41,8 @@ private:
public:
EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) {
}
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing EspBox3Board");
InitializeI2c();
InitializeButtons();
WifiBoard::Initialize();
}
virtual Led* GetBuiltinLed() override {

View File

@@ -122,18 +122,12 @@ public:
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
}
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
InitializeDisplayI2c();
InitializeCodecI2c();
MountStorage();
Enable4GModule();
InitializeButtons();
Ml307Board::Initialize();
}
virtual Led* GetBuiltinLed() override {

View File

@@ -163,10 +163,6 @@ public:
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
}
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
InitializeDisplayI2c();
InitializeCodecI2c();
axp2101_ = new Axp2101(codec_i2c_bus_, AXP2101_I2C_ADDR);
@@ -176,8 +172,6 @@ public:
InitializeButtons();
InitializePowerSaveTimer();
Ml307Board::Initialize();
}
virtual Led* GetBuiltinLed() override {

View File

@@ -49,17 +49,9 @@ private:
}
public:
KevinBoxBoard() :
boot_button_(BOOT_BUTTON_GPIO) {
}
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeCodecI2c();
InitializeButtons();
WifiBoard::Initialize();
}
virtual Led* GetBuiltinLed() override {

View File

@@ -121,15 +121,10 @@ private:
public:
LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) {
}
virtual void Initialize() override {
ESP_LOGI(TAG, "Initializing LichuangDevBoard");
InitializeI2c();
InitializeSpi();
InitializeSt7789Display();
InitializeButtons();
WifiBoard::Initialize();
}
virtual Led* GetBuiltinLed() override {

View File

@@ -0,0 +1,38 @@
[
{
"name": "lamp",
"description": "A lamp",
"properties": {
"power": {
"type": "boolean",
"description": "Whether the lamp is on or off"
}
},
"methods": {
"TurnOn": {
"description": "Turns the lamp on"
}
}
},
{
"name": "speaker",
"description": "当前 AI 机器人的扬声器",
"properties": {
"volume": {
"type": "number",
"description": "当前扬声器的音量0-100"
}
},
"methods": {
"SetVolume": {
"description": "设置当前扬声器的音量",
"parameters": {
"volume": {
"type": "number",
"description": "The volume of the speaker (0-100)"
}
}
}
}
}
]

77
main/iot/thing.cc Normal file
View File

@@ -0,0 +1,77 @@
#include "thing.h"
#include "application.h"
#include <esp_log.h>
#define TAG "Thing"
namespace iot {
static std::map<std::string, std::function<Thing*()>>* thing_creators = nullptr;
void RegisterThing(const std::string& type, std::function<Thing*()> creator) {
if (thing_creators == nullptr) {
thing_creators = new std::map<std::string, std::function<Thing*()>>();
}
(*thing_creators)[type] = creator;
}
Thing* CreateThing(const std::string& type) {
auto creator = thing_creators->find(type);
if (creator == thing_creators->end()) {
ESP_LOGE(TAG, "Thing type not found: %s", type.c_str());
return nullptr;
}
return creator->second();
}
std::string Thing::GetDescriptorJson() {
std::string json_str = "{";
json_str += "\"name\":\"" + name_ + "\",";
json_str += "\"description\":\"" + description_ + "\",";
json_str += "\"properties\":" + properties_.GetDescriptorJson() + ",";
json_str += "\"methods\":" + methods_.GetDescriptorJson();
json_str += "}";
return json_str;
}
std::string Thing::GetStateJson() {
std::string json_str = "{";
json_str += "\"name\":\"" + name_ + "\",";
json_str += "\"state\":" + properties_.GetStateJson();
json_str += "}";
return json_str;
}
void Thing::Invoke(const cJSON* command) {
auto method_name = cJSON_GetObjectItem(command, "method");
auto input_params = cJSON_GetObjectItem(command, "parameters");
try {
auto& method = methods_[method_name->valuestring];
for (auto& param : method.parameters()) {
auto input_param = cJSON_GetObjectItem(input_params, param.name().c_str());
if (param.required() && input_param == nullptr) {
throw std::runtime_error("Parameter " + param.name() + " is required");
}
if (param.type() == kValueTypeNumber) {
param.set_number(input_param->valueint);
} else if (param.type() == kValueTypeString) {
param.set_string(input_param->valuestring);
} else if (param.type() == kValueTypeBoolean) {
param.set_boolean(input_param->valueint == 1);
}
}
Application::GetInstance().Schedule([&method]() {
method.Invoke();
});
} catch (const std::runtime_error& e) {
ESP_LOGE(TAG, "Method not found: %s", method_name->valuestring);
return;
}
}
} // namespace iot

300
main/iot/thing.h Normal file
View File

@@ -0,0 +1,300 @@
#ifndef THING_H
#define THING_H
#include <string>
#include <map>
#include <functional>
#include <vector>
#include <stdexcept>
#include <cJSON.h>
namespace iot {
enum ValueType {
kValueTypeBoolean,
kValueTypeNumber,
kValueTypeString
};
class Property {
private:
std::string name_;
std::string description_;
ValueType type_;
std::function<bool()> boolean_getter_;
std::function<int()> number_getter_;
std::function<std::string()> string_getter_;
public:
Property(const std::string& name, const std::string& description, std::function<bool()> getter) :
name_(name), description_(description), type_(kValueTypeBoolean), boolean_getter_(getter) {}
Property(const std::string& name, const std::string& description, std::function<int()> getter) :
name_(name), description_(description), type_(kValueTypeNumber), number_getter_(getter) {}
Property(const std::string& name, const std::string& description, std::function<std::string()> getter) :
name_(name), description_(description), type_(kValueTypeString), string_getter_(getter) {}
const std::string& name() const { return name_; }
const std::string& description() const { return description_; }
ValueType type() const { return type_; }
bool boolean() const { return boolean_getter_(); }
int number() const { return number_getter_(); }
std::string string() const { return string_getter_(); }
std::string GetDescriptorJson() {
std::string json_str = "{";
json_str += "\"description\":\"" + description_ + "\",";
if (type_ == kValueTypeBoolean) {
json_str += "\"type\":\"boolean\"";
} else if (type_ == kValueTypeNumber) {
json_str += "\"type\":\"number\"";
} else if (type_ == kValueTypeString) {
json_str += "\"type\":\"string\"";
}
json_str += "}";
return json_str;
}
std::string GetStateJson() {
if (type_ == kValueTypeBoolean) {
return boolean_getter_() ? "true" : "false";
} else if (type_ == kValueTypeNumber) {
return std::to_string(number_getter_());
} else if (type_ == kValueTypeString) {
return "\"" + string_getter_() + "\"";
}
return "null";
}
};
class PropertyList {
private:
std::vector<Property> properties_;
public:
PropertyList() = default;
PropertyList(const std::vector<Property>& properties) : properties_(properties) {}
void AddBooleanProperty(const std::string& name, const std::string& description, std::function<bool()> getter) {
properties_.push_back(Property(name, description, getter));
}
void AddNumberProperty(const std::string& name, const std::string& description, std::function<int()> getter) {
properties_.push_back(Property(name, description, getter));
}
void AddStringProperty(const std::string& name, const std::string& description, std::function<std::string()> getter) {
properties_.push_back(Property(name, description, getter));
}
const Property& operator[](const std::string& name) const {
for (auto& property : properties_) {
if (property.name() == name) {
return property;
}
}
throw std::runtime_error("Property not found: " + name);
}
std::string GetDescriptorJson() {
std::string json_str = "{";
for (auto& property : properties_) {
json_str += "\"" + property.name() + "\":" + property.GetDescriptorJson() + ",";
}
if (json_str.back() == ',') {
json_str.pop_back();
}
json_str += "}";
return json_str;
}
std::string GetStateJson() {
std::string json_str = "{";
for (auto& property : properties_) {
json_str += "\"" + property.name() + "\":" + property.GetStateJson() + ",";
}
if (json_str.back() == ',') {
json_str.pop_back();
}
json_str += "}";
return json_str;
}
};
class Parameter {
private:
std::string name_;
std::string description_;
ValueType type_;
bool required_;
bool boolean_;
int number_;
std::string string_;
public:
Parameter(const std::string& name, const std::string& description, ValueType type, bool required = true) :
name_(name), description_(description), type_(type), required_(required) {}
const std::string& name() const { return name_; }
const std::string& description() const { return description_; }
ValueType type() const { return type_; }
bool required() const { return required_; }
bool boolean() const { return boolean_; }
int number() const { return number_; }
const std::string& string() const { return string_; }
void set_boolean(bool value) { boolean_ = value; }
void set_number(int value) { number_ = value; }
void set_string(const std::string& value) { string_ = value; }
std::string GetDescriptorJson() {
std::string json_str = "{";
json_str += "\"description\":\"" + description_ + "\",";
if (type_ == kValueTypeBoolean) {
json_str += "\"type\":\"boolean\"";
} else if (type_ == kValueTypeNumber) {
json_str += "\"type\":\"number\"";
} else if (type_ == kValueTypeString) {
json_str += "\"type\":\"string\"";
}
json_str += "}";
return json_str;
}
};
class ParameterList {
private:
std::vector<Parameter> parameters_;
public:
ParameterList() = default;
ParameterList(const std::vector<Parameter>& parameters) : parameters_(parameters) {}
void AddParameter(const Parameter& parameter) {
parameters_.push_back(parameter);
}
const Parameter& operator[](const std::string& name) const {
for (auto& parameter : parameters_) {
if (parameter.name() == name) {
return parameter;
}
}
throw std::runtime_error("Parameter not found: " + name);
}
// iterator
auto begin() { return parameters_.begin(); }
auto end() { return parameters_.end(); }
std::string GetDescriptorJson() {
std::string json_str = "{";
for (auto& parameter : parameters_) {
json_str += "\"" + parameter.name() + "\":" + parameter.GetDescriptorJson() + ",";
}
if (json_str.back() == ',') {
json_str.pop_back();
}
json_str += "}";
return json_str;
}
};
class Method {
private:
std::string name_;
std::string description_;
ParameterList parameters_;
std::function<void(const ParameterList&)> callback_;
public:
Method(const std::string& name, const std::string& description, const ParameterList& parameters, std::function<void(const ParameterList&)> callback) :
name_(name), description_(description), parameters_(parameters), callback_(callback) {}
const std::string& name() const { return name_; }
const std::string& description() const { return description_; }
ParameterList& parameters() { return parameters_; }
std::string GetDescriptorJson() {
std::string json_str = "{";
json_str += "\"description\":\"" + description_ + "\",";
json_str += "\"parameters\":" + parameters_.GetDescriptorJson();
json_str += "}";
return json_str;
}
void Invoke() {
callback_(parameters_);
}
};
class MethodList {
private:
std::vector<Method> methods_;
public:
MethodList() = default;
MethodList(const std::vector<Method>& methods) : methods_(methods) {}
void AddMethod(const std::string& name, const std::string& description, const ParameterList& parameters, std::function<void(const ParameterList&)> callback) {
methods_.push_back(Method(name, description, parameters, callback));
}
Method& operator[](const std::string& name) {
for (auto& method : methods_) {
if (method.name() == name) {
return method;
}
}
throw std::runtime_error("Method not found: " + name);
}
std::string GetDescriptorJson() {
std::string json_str = "{";
for (auto& method : methods_) {
json_str += "\"" + method.name() + "\":" + method.GetDescriptorJson() + ",";
}
if (json_str.back() == ',') {
json_str.pop_back();
}
json_str += "}";
return json_str;
}
};
class Thing {
public:
Thing(const std::string& name, const std::string& description) :
name_(name), description_(description) {}
virtual ~Thing() = default;
virtual std::string GetDescriptorJson();
virtual std::string GetStateJson();
virtual void Invoke(const cJSON* command);
const std::string& name() const { return name_; }
const std::string& description() const { return description_; }
protected:
PropertyList properties_;
MethodList methods_;
private:
std::string name_;
std::string description_;
};
void RegisterThing(const std::string& type, std::function<Thing*()> creator);
Thing* CreateThing(const std::string& type);
#define DECLARE_THING(TypeName) \
static iot::Thing* Create##TypeName() { \
return new iot::TypeName(); \
} \
static bool Register##TypeNameHelper = []() { \
RegisterThing(#TypeName, Create##TypeName); \
return true; \
}();
} // namespace iot
#endif // THING_H

47
main/iot/thing_manager.cc Normal file
View File

@@ -0,0 +1,47 @@
#include "thing_manager.h"
#include <esp_log.h>
#define TAG "ThingManager"
namespace iot {
void ThingManager::AddThing(Thing* thing) {
things_.push_back(thing);
}
std::string ThingManager::GetDescriptorsJson() {
std::string json_str = "[";
for (auto& thing : things_) {
json_str += thing->GetDescriptorJson() + ",";
}
if (json_str.back() == ',') {
json_str.pop_back();
}
json_str += "]";
return json_str;
}
std::string ThingManager::GetStatesJson() {
std::string json_str = "[";
for (auto& thing : things_) {
json_str += thing->GetStateJson() + ",";
}
if (json_str.back() == ',') {
json_str.pop_back();
}
json_str += "]";
return json_str;
}
void ThingManager::Invoke(const cJSON* command) {
auto name = cJSON_GetObjectItem(command, "name");
for (auto& thing : things_) {
if (thing->name() == name->valuestring) {
thing->Invoke(command);
return;
}
}
}
} // namespace iot

41
main/iot/thing_manager.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef THING_MANAGER_H
#define THING_MANAGER_H
#include "thing.h"
#include <cJSON.h>
#include <vector>
#include <memory>
#include <functional>
#include <map>
namespace iot {
class ThingManager {
public:
static ThingManager& GetInstance() {
static ThingManager instance;
return instance;
}
ThingManager(const ThingManager&) = delete;
ThingManager& operator=(const ThingManager&) = delete;
void AddThing(Thing* thing);
std::string GetDescriptorsJson();
std::string GetStatesJson();
void Invoke(const cJSON* command);
private:
ThingManager() = default;
~ThingManager() = default;
std::vector<Thing*> things_;
};
} // namespace iot
#endif // THING_MANAGER_H

54
main/iot/things/lamp.cc Normal file
View File

@@ -0,0 +1,54 @@
#include "iot/thing.h"
#include "board.h"
#include "audio_codec.h"
#include <driver/gpio.h>
#include <esp_log.h>
#define TAG "Lamp"
namespace iot {
// 这里仅定义 Lamp 的属性和方法,不包含具体的实现
class Lamp : public Thing {
private:
gpio_num_t gpio_num_ = GPIO_NUM_18;
bool power_ = false;
void InitializeGpio() {
gpio_config_t config = {
.pin_bit_mask = (1ULL << gpio_num_),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&config));
gpio_set_level(gpio_num_, 0);
}
public:
Lamp() : Thing("Lamp", "一个测试用的灯"), power_(false) {
InitializeGpio();
// 定义设备的属性
properties_.AddBooleanProperty("power", "灯是否打开", [this]() -> bool {
return power_;
});
// 定义设备可以被远程执行的指令
methods_.AddMethod("TurnOn", "打开灯", ParameterList(), [this](const ParameterList& parameters) {
power_ = true;
gpio_set_level(gpio_num_, 1);
});
methods_.AddMethod("TurnOff", "关闭灯", ParameterList(), [this](const ParameterList& parameters) {
power_ = false;
gpio_set_level(gpio_num_, 0);
});
}
};
} // namespace iot
DECLARE_THING(Lamp);

View File

@@ -0,0 +1,33 @@
#include "iot/thing.h"
#include "board.h"
#include "audio_codec.h"
#include <esp_log.h>
#define TAG "Speaker"
namespace iot {
// 这里仅定义 Speaker 的属性和方法,不包含具体的实现
class Speaker : public Thing {
public:
Speaker() : Thing("Speaker", "当前 AI 机器人的扬声器") {
// 定义设备的属性
properties_.AddNumberProperty("volume", "当前音量值", [this]() -> int {
auto codec = Board::GetInstance().GetAudioCodec();
return codec->output_volume();
});
// 定义设备可以被远程执行的指令
methods_.AddMethod("SetVolume", "设置音量", ParameterList({
Parameter("volume", "0到100之间的整数", kValueTypeNumber, true)
}), [this](const ParameterList& parameters) {
auto codec = Board::GetInstance().GetAudioCodec();
codec->SetOutputVolume(static_cast<uint8_t>(parameters["volume"].number()));
});
}
};
} // namespace iot
DECLARE_THING(Speaker);

View File

@@ -57,3 +57,14 @@ void Protocol::SendStopListening() {
std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"listen\",\"state\":\"stop\"}";
SendText(message);
}
void Protocol::SendIotDescriptors(const std::string& descriptors) {
std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"iot\",\"descriptors\":" + descriptors + "}";
SendText(message);
}
void Protocol::SendIotStates(const std::string& states) {
std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"iot\",\"states\":" + states + "}";
SendText(message);
}

View File

@@ -45,6 +45,8 @@ public:
virtual void SendStartListening(ListeningMode mode);
virtual void SendStopListening();
virtual void SendAbortSpeaking(AbortReason reason);
virtual void SendIotDescriptors(const std::string& descriptors);
virtual void SendIotStates(const std::string& states);
protected:
std::function<void(const cJSON* root)> on_incoming_json_;

View File

@@ -1,3 +1,6 @@
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y