diff --git a/CMakeLists.txt b/CMakeLists.txt index 12cbc4f2..fd1762e0 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 40fb970e..ad69202a 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -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 diff --git a/main/application.cc b/main/application.cc index 40d93c46..3119e000 100644 --- a/main/application.cc +++ b/main/application.cc @@ -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 #include @@ -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); + } +} diff --git a/main/application.h b/main/application.h index 889d635d..c31f803b 100644 --- a/main/application.h +++ b/main/application.h @@ -4,18 +4,16 @@ #include #include #include -#include + +#include #include #include -#include -#include "opus_encoder.h" -#include "opus_decoder.h" -#include "opus_resampler.h" +#include +#include +#include #include "protocol.h" -#include "display.h" -#include "board.h" #include "ota.h" #include "background_task.h" @@ -58,6 +56,7 @@ class Application { void ToggleChatState(); void StartListening(); void StopListening(); + void UpdateIotStates(); private: Application(); @@ -75,6 +74,7 @@ class Application { volatile ChatState chat_state_ = kChatStateUnknown; bool keep_listening_ = false; bool aborted_ = false; + std::string last_iot_states_; // Audio encode / decode BackgroundTask background_task_; diff --git a/main/boards/bread-compact-ml307/compact_ml307_board.cc b/main/boards/bread-compact-ml307/compact_ml307_board.cc index f48508a8..bca70e87 100644 --- a/main/boards/bread-compact-ml307/compact_ml307_board.cc +++ b/main/boards/bread-compact-ml307/compact_ml307_board.cc @@ -6,6 +6,7 @@ #include "button.h" #include "led.h" #include "config.h" +#include "iot/thing_manager.h" #include #include @@ -59,8 +60,7 @@ class CompactMl307Board : public Ml307Board { }); volume_up_button_.OnLongPress([this]() { - auto codec = GetAudioCodec(); - codec->SetOutputVolume(100); + GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification("最大音量"); }); @@ -75,12 +75,18 @@ class CompactMl307Board : public Ml307Board { }); 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 @@ class CompactMl307Board : public Ml307Board { 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 { diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index bf345b8a..5d9ac8d8 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -6,6 +6,7 @@ #include "button.h" #include "led.h" #include "config.h" +#include "iot/thing_manager.h" #include #include @@ -64,8 +65,7 @@ class CompactWifiBoard : public WifiBoard { }); volume_up_button_.OnLongPress([this]() { - auto codec = GetAudioCodec(); - codec->SetOutputVolume(100); + GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification("最大音量"); }); @@ -80,30 +80,31 @@ class CompactWifiBoard : public WifiBoard { }); 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 { diff --git a/main/boards/common/board.cc b/main/boards/common/board.cc index 4398cbf1..e43b210f 100644 --- a/main/boards/common/board.cc +++ b/main/boards/common/board.cc @@ -6,7 +6,10 @@ #include #include -// static const char *TAG = "Board"; +#define TAG "Board" + +Board::Board() { +} bool Board::GetBatteryLevel(int &level, bool& charging) { return false; diff --git a/main/boards/common/board.h b/main/boards/common/board.h index a0193e15..66de827d 100644 --- a/main/boards/common/board.h +++ b/main/boards/common/board.h @@ -19,7 +19,7 @@ class Board { virtual std::string GetBoardJson() = 0; protected: - Board() = default; + Board(); public: static Board& GetInstance() { @@ -30,7 +30,6 @@ class Board { return *instance; } - virtual void Initialize() = 0; virtual void StartNetwork() = 0; virtual ~Board() = default; virtual Led* GetBuiltinLed() = 0; diff --git a/main/boards/common/ml307_board.cc b/main/boards/common/ml307_board.cc index 231e225e..3efd8bcd 100644 --- a/main/boards/common/ml307_board.cc +++ b/main/boards/common/ml307_board.cc @@ -1,5 +1,7 @@ #include "ml307_board.h" + #include "application.h" +#include "display.h" #include "font_awesome_symbols.h" #include @@ -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() { diff --git a/main/boards/common/ml307_board.h b/main/boards/common/ml307_board.h index b2ab46da..3400dabc 100644 --- a/main/boards/common/ml307_board.h +++ b/main/boards/common/ml307_board.h @@ -13,7 +13,6 @@ class Ml307Board : public Board { 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; diff --git a/main/boards/common/wifi_board.cc b/main/boards/common/wifi_board.cc index 0d368045..31989043 100644 --- a/main/boards/common/wifi_board.cc +++ b/main/boards/common/wifi_board.cc @@ -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(); } diff --git a/main/boards/common/wifi_board.h b/main/boards/common/wifi_board.h index 546039a2..c16e7ef4 100644 --- a/main/boards/common/wifi_board.h +++ b/main/boards/common/wifi_board.h @@ -10,7 +10,6 @@ class WifiBoard : public Board { virtual std::string GetBoardJson() override; public: - virtual void Initialize() override; virtual void StartNetwork() override; virtual Http* CreateHttp() override; virtual WebSocket* CreateWebSocket() override; diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc index ce2f9452..919ef8fc 100644 --- a/main/boards/esp-box-3/esp_box3_board.cc +++ b/main/boards/esp-box-3/esp_box3_board.cc @@ -41,13 +41,8 @@ class EspBox3Board : public WifiBoard { public: EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) { - } - - virtual void Initialize() override { - ESP_LOGI(TAG, "Initializing EspBox3Board"); InitializeI2c(); InitializeButtons(); - WifiBoard::Initialize(); } virtual Led* GetBuiltinLed() override { diff --git a/main/boards/kevin-box-1/kevin_box_board.cc b/main/boards/kevin-box-1/kevin_box_board.cc index a1e75208..d37938ea 100644 --- a/main/boards/kevin-box-1/kevin_box_board.cc +++ b/main/boards/kevin-box-1/kevin_box_board.cc @@ -122,18 +122,12 @@ class KevinBoxBoard : public Ml307Board { 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 { diff --git a/main/boards/kevin-box-2/kevin_box_board.cc b/main/boards/kevin-box-2/kevin_box_board.cc index a76c3e53..e5124c83 100644 --- a/main/boards/kevin-box-2/kevin_box_board.cc +++ b/main/boards/kevin-box-2/kevin_box_board.cc @@ -163,10 +163,6 @@ class KevinBoxBoard : public Ml307Board { 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 @@ class KevinBoxBoard : public Ml307Board { InitializeButtons(); InitializePowerSaveTimer(); - - Ml307Board::Initialize(); } virtual Led* GetBuiltinLed() override { diff --git a/main/boards/kevin-c3/kevin_box_board.cc b/main/boards/kevin-c3/kevin_box_board.cc index 47c8c791..b0fd4dac 100644 --- a/main/boards/kevin-c3/kevin_box_board.cc +++ b/main/boards/kevin-c3/kevin_box_board.cc @@ -49,17 +49,9 @@ class KevinBoxBoard : public WifiBoard { } 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 { diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index 058cf049..915567af 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -121,15 +121,10 @@ class LichuangDevBoard : public WifiBoard { 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 { diff --git a/main/iot/sample_interface.json b/main/iot/sample_interface.json new file mode 100644 index 00000000..3dcd3039 --- /dev/null +++ b/main/iot/sample_interface.json @@ -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)" + } + } + } + } + } +] \ No newline at end of file diff --git a/main/iot/thing.cc b/main/iot/thing.cc new file mode 100644 index 00000000..88e4fc75 --- /dev/null +++ b/main/iot/thing.cc @@ -0,0 +1,77 @@ +#include "thing.h" +#include "application.h" + +#include + +#define TAG "Thing" + + +namespace iot { + +static std::map>* thing_creators = nullptr; + +void RegisterThing(const std::string& type, std::function creator) { + if (thing_creators == nullptr) { + thing_creators = new std::map>(); + } + (*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 diff --git a/main/iot/thing.h b/main/iot/thing.h new file mode 100644 index 00000000..21c8d691 --- /dev/null +++ b/main/iot/thing.h @@ -0,0 +1,300 @@ +#ifndef THING_H +#define THING_H + +#include +#include +#include +#include +#include +#include + +namespace iot { + +enum ValueType { + kValueTypeBoolean, + kValueTypeNumber, + kValueTypeString +}; + +class Property { +private: + std::string name_; + std::string description_; + ValueType type_; + std::function boolean_getter_; + std::function number_getter_; + std::function string_getter_; + +public: + Property(const std::string& name, const std::string& description, std::function getter) : + name_(name), description_(description), type_(kValueTypeBoolean), boolean_getter_(getter) {} + Property(const std::string& name, const std::string& description, std::function getter) : + name_(name), description_(description), type_(kValueTypeNumber), number_getter_(getter) {} + Property(const std::string& name, const std::string& description, std::function 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 properties_; + +public: + PropertyList() = default; + PropertyList(const std::vector& properties) : properties_(properties) {} + + void AddBooleanProperty(const std::string& name, const std::string& description, std::function getter) { + properties_.push_back(Property(name, description, getter)); + } + void AddNumberProperty(const std::string& name, const std::string& description, std::function getter) { + properties_.push_back(Property(name, description, getter)); + } + void AddStringProperty(const std::string& name, const std::string& description, std::function 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 parameters_; + +public: + ParameterList() = default; + ParameterList(const std::vector& 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 callback_; + +public: + Method(const std::string& name, const std::string& description, const ParameterList& parameters, std::function 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 methods_; + +public: + MethodList() = default; + MethodList(const std::vector& methods) : methods_(methods) {} + + void AddMethod(const std::string& name, const std::string& description, const ParameterList& parameters, std::function 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 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 diff --git a/main/iot/thing_manager.cc b/main/iot/thing_manager.cc new file mode 100644 index 00000000..9c1c11e3 --- /dev/null +++ b/main/iot/thing_manager.cc @@ -0,0 +1,47 @@ +#include "thing_manager.h" + +#include + +#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 diff --git a/main/iot/thing_manager.h b/main/iot/thing_manager.h new file mode 100644 index 00000000..b212c3dc --- /dev/null +++ b/main/iot/thing_manager.h @@ -0,0 +1,41 @@ +#ifndef THING_MANAGER_H +#define THING_MANAGER_H + + +#include "thing.h" + +#include + +#include +#include +#include +#include + +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 things_; +}; + + +} // namespace iot + +#endif // THING_MANAGER_H diff --git a/main/iot/things/lamp.cc b/main/iot/things/lamp.cc new file mode 100644 index 00000000..392b36cd --- /dev/null +++ b/main/iot/things/lamp.cc @@ -0,0 +1,54 @@ +#include "iot/thing.h" +#include "board.h" +#include "audio_codec.h" + +#include +#include + +#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); diff --git a/main/iot/things/speaker.cc b/main/iot/things/speaker.cc new file mode 100644 index 00000000..7dc70fc3 --- /dev/null +++ b/main/iot/things/speaker.cc @@ -0,0 +1,33 @@ +#include "iot/thing.h" +#include "board.h" +#include "audio_codec.h" + +#include + +#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(parameters["volume"].number())); + }); + } +}; + +} // namespace iot + +DECLARE_THING(Speaker); diff --git a/main/protocols/protocol.cc b/main/protocols/protocol.cc index 12ecd951..dae93237 100644 --- a/main/protocols/protocol.cc +++ b/main/protocols/protocol.cc @@ -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); +} + diff --git a/main/protocols/protocol.h b/main/protocols/protocol.h index f51d549e..913f54ff 100644 --- a/main/protocols/protocol.h +++ b/main/protocols/protocol.h @@ -45,6 +45,8 @@ class Protocol { 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 on_incoming_json_; diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 1b79fd81..6caaaa61 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -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