Skip to content

Commit 5da6677

Browse files
committed
Add MCP server
1 parent f142c54 commit 5da6677

23 files changed

+845
-41
lines changed

main/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ set(SOURCES "audio_codecs/audio_codec.cc"
1515
"protocols/websocket_protocol.cc"
1616
"iot/thing.cc"
1717
"iot/thing_manager.cc"
18+
"mcp_server.cc"
1819
"system_info.cc"
1920
"application.cc"
2021
"ota.cc"

main/Kconfig.projbuild

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ config OTA_URL
88

99

1010
choice
11-
prompt "语言选择"
11+
prompt "Default Language"
1212
default LANGUAGE_ZH_CN
1313
help
1414
Select device display language
@@ -249,37 +249,48 @@ choice DISPLAY_ESP32S3_KORVO2_V3
249249
endchoice
250250

251251
config USE_WECHAT_MESSAGE_STYLE
252-
bool "使用微信聊天界面风格"
252+
bool "Enable WeChat Message Style"
253253
default n
254254
help
255255
使用微信聊天界面风格
256256

257257
config USE_WAKE_WORD_DETECT
258-
bool "启用唤醒词检测"
258+
bool "Enable Wake Word Detection"
259259
default y
260260
depends on IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4 && SPIRAM
261261
help
262262
需要 ESP32 S3 与 AFE 支持
263263

264264
config USE_AUDIO_PROCESSOR
265-
bool "启用音频降噪、增益处理"
265+
bool "Enable Audio Noise Reduction"
266266
default y
267267
depends on IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4 && SPIRAM
268268
help
269269
需要 ESP32 S3 与 AFE 支持
270270

271271
config USE_DEVICE_AEC
272-
bool "在通话过程中启用设备端 AEC"
272+
bool "Enable Device-Side AEC"
273273
default n
274274
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV || BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75)
275275
help
276276
因为性能不够,不建议和微信聊天界面风格同时开启
277277

278278
config USE_SERVER_AEC
279-
bool "在通话过程中启用服务器端 AEC"
279+
bool "Enable Server-Side AEC"
280280
default n
281281
depends on USE_AUDIO_PROCESSOR
282282
help
283283
启用服务器端 AEC,需要服务器支持
284284

285+
choice IOT_PROTOCOL
286+
prompt "IoT Protocol"
287+
default IOT_PROTOCOL_XIAOZHI
288+
help
289+
IoT 协议,用于获取设备状态与发送控制指令
290+
config IOT_PROTOCOL_MCP
291+
bool "MCP协议 2024-11-05"
292+
config IOT_PROTOCOL_XIAOZHI
293+
bool "小智IoT协议 1.0"
294+
endchoice
295+
285296
endmenu

main/application.cc

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "font_awesome_symbols.h"
1010
#include "iot/thing_manager.h"
1111
#include "assets/lang_config.h"
12+
#include "mcp_server.h"
1213

1314
#if CONFIG_USE_AUDIO_PROCESSOR
1415
#include "afe_audio_processor.h"
@@ -41,7 +42,7 @@ static const char* const STATE_STRINGS[] = {
4142

4243
Application::Application() {
4344
event_group_ = xEventGroupCreate();
44-
background_task_ = new BackgroundTask(4096 * 8);
45+
background_task_ = new BackgroundTask(4096 * 7);
4546

4647
#if CONFIG_USE_AUDIO_PROCESSOR
4748
audio_processor_ = std::make_unique<AfeAudioProcessor>();
@@ -425,12 +426,15 @@ void Application::Start() {
425426
protocol_->server_sample_rate(), codec->output_sample_rate());
426427
}
427428
SetDecodeSampleRate(protocol_->server_sample_rate(), protocol_->server_frame_duration());
429+
430+
#if CONFIG_IOT_PROTOCOL_XIAOZHI
428431
auto& thing_manager = iot::ThingManager::GetInstance();
429432
protocol_->SendIotDescriptors(thing_manager.GetDescriptorsJson());
430433
std::string states;
431434
if (thing_manager.GetStatesJson(states, false)) {
432435
protocol_->SendIotStates(states);
433436
}
437+
#endif
434438
});
435439
protocol_->OnAudioChannelClosed([this, &board]() {
436440
board.SetPowerSaveMode(true);
@@ -465,7 +469,7 @@ void Application::Start() {
465469
});
466470
} else if (strcmp(state->valuestring, "sentence_start") == 0) {
467471
auto text = cJSON_GetObjectItem(root, "text");
468-
if (text != NULL) {
472+
if (cJSON_IsString(text)) {
469473
ESP_LOGI(TAG, "<< %s", text->valuestring);
470474
Schedule([this, display, message = std::string(text->valuestring)]() {
471475
display->SetChatMessage("assistant", message.c_str());
@@ -474,31 +478,40 @@ void Application::Start() {
474478
}
475479
} else if (strcmp(type->valuestring, "stt") == 0) {
476480
auto text = cJSON_GetObjectItem(root, "text");
477-
if (text != NULL) {
481+
if (cJSON_IsString(text)) {
478482
ESP_LOGI(TAG, ">> %s", text->valuestring);
479483
Schedule([this, display, message = std::string(text->valuestring)]() {
480484
display->SetChatMessage("user", message.c_str());
481485
});
482486
}
483487
} else if (strcmp(type->valuestring, "llm") == 0) {
484488
auto emotion = cJSON_GetObjectItem(root, "emotion");
485-
if (emotion != NULL) {
489+
if (cJSON_IsString(emotion)) {
486490
Schedule([this, display, emotion_str = std::string(emotion->valuestring)]() {
487491
display->SetEmotion(emotion_str.c_str());
488492
});
489493
}
494+
#if CONFIG_IOT_PROTOCOL_MCP
495+
} else if (strcmp(type->valuestring, "mcp") == 0) {
496+
auto payload = cJSON_GetObjectItem(root, "payload");
497+
if (cJSON_IsObject(payload)) {
498+
McpServer::GetInstance().ParseMessage(payload);
499+
}
500+
#endif
501+
#if CONFIG_IOT_PROTOCOL_XIAOZHI
490502
} else if (strcmp(type->valuestring, "iot") == 0) {
491503
auto commands = cJSON_GetObjectItem(root, "commands");
492-
if (commands != NULL) {
504+
if (cJSON_IsArray(commands)) {
493505
auto& thing_manager = iot::ThingManager::GetInstance();
494506
for (int i = 0; i < cJSON_GetArraySize(commands); ++i) {
495507
auto command = cJSON_GetArrayItem(commands, i);
496508
thing_manager.Invoke(command);
497509
}
498510
}
511+
#endif
499512
} else if (strcmp(type->valuestring, "system") == 0) {
500513
auto command = cJSON_GetObjectItem(root, "command");
501-
if (command != NULL) {
514+
if (cJSON_IsString(command)) {
502515
ESP_LOGI(TAG, "System command: %s", command->valuestring);
503516
if (strcmp(command->valuestring, "reboot") == 0) {
504517
// Do a reboot if user requests a OTA update
@@ -513,7 +526,7 @@ void Application::Start() {
513526
auto status = cJSON_GetObjectItem(root, "status");
514527
auto message = cJSON_GetObjectItem(root, "message");
515528
auto emotion = cJSON_GetObjectItem(root, "emotion");
516-
if (status != NULL && message != NULL && emotion != NULL) {
529+
if (cJSON_IsString(status) && cJSON_IsString(message) && cJSON_IsString(emotion)) {
517530
Alert(status->valuestring, message->valuestring, emotion->valuestring, Lang::Sounds::P3_VIBRATION);
518531
} else {
519532
ESP_LOGW(TAG, "Alert command requires status, message and emotion");
@@ -620,7 +633,10 @@ void Application::OnClockTimer() {
620633
clock_ticks_++;
621634

622635
// Print the debug info every 10 seconds
623-
if (clock_ticks_ % 10 == 0) {
636+
if (clock_ticks_ % 3 == 0) {
637+
// char buffer[500];
638+
// vTaskList(buffer);
639+
// ESP_LOGI(TAG, "Task list: \n%s", buffer);
624640
// SystemInfo::PrintRealTimeStats(pdMS_TO_TICKS(1000));
625641

626642
int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
@@ -850,7 +866,9 @@ void Application::SetDeviceState(DeviceState state) {
850866
display->SetStatus(Lang::Strings::LISTENING);
851867
display->SetEmotion("neutral");
852868
// Update the IoT states before sending the start listening command
869+
#if CONFIG_IOT_PROTOCOL_XIAOZHI
853870
UpdateIotStates();
871+
#endif
854872

855873
// Make sure the audio processor is running
856874
if (!audio_processor_->IsRunning()) {
@@ -910,11 +928,13 @@ void Application::SetDecodeSampleRate(int sample_rate, int frame_duration) {
910928
}
911929

912930
void Application::UpdateIotStates() {
931+
#if CONFIG_IOT_PROTOCOL_XIAOZHI
913932
auto& thing_manager = iot::ThingManager::GetInstance();
914933
std::string states;
915934
if (thing_manager.GetStatesJson(states, true)) {
916935
protocol_->SendIotStates(states);
917936
}
937+
#endif
918938
}
919939

920940
void Application::Reboot() {
@@ -955,3 +975,11 @@ bool Application::CanEnterSleepMode() {
955975
// Now it is safe to enter sleep mode
956976
return true;
957977
}
978+
979+
void Application::SendMcpMessage(const std::string& payload) {
980+
Schedule([this, payload]() {
981+
if (protocol_) {
982+
protocol_->SendMcpMessage(payload);
983+
}
984+
});
985+
}

main/application.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class Application {
7272
void WakeWordInvoke(const std::string& wake_word);
7373
void PlaySound(const std::string_view& sound);
7474
bool CanEnterSleepMode();
75+
void SendMcpMessage(const std::string& payload);
7576

7677
private:
7778
Application();

main/boards/bread-compact-wifi/compact_wifi_board.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class CompactWifiBoard : public WifiBoard {
9494
display_ = new NoDisplay();
9595
return;
9696
}
97+
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false));
9798

9899
// Set the display to on
99100
ESP_LOGI(TAG, "Turning display on");

main/boards/common/board.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class Board {
4949
virtual std::string GetJson();
5050
virtual void SetPowerSaveMode(bool enabled) = 0;
5151
virtual std::string GetBoardJson() = 0;
52+
virtual std::string GetDeviceStatusJson() = 0;
5253
};
5354

5455
#define DECLARE_BOARD(BOARD_CLASS_NAME) \

main/boards/common/dual_network_board.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,8 @@ void DualNetworkBoard::SetPowerSaveMode(bool enabled) {
9898

9999
std::string DualNetworkBoard::GetBoardJson() {
100100
return current_board_->GetBoardJson();
101-
}
101+
}
102+
103+
std::string DualNetworkBoard::GetDeviceStatusJson() {
104+
return current_board_->GetDeviceStatusJson();
105+
}

main/boards/common/dual_network_board.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class DualNetworkBoard : public Board {
5656
virtual const char* GetNetworkStateIcon() override;
5757
virtual void SetPowerSaveMode(bool enabled) override;
5858
virtual std::string GetBoardJson() override;
59-
59+
virtual std::string GetDeviceStatusJson() override;
6060
};
6161

6262
#endif // DUAL_NETWORK_BOARD_H

main/boards/common/ml307_board.cc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,86 @@ std::string Ml307Board::GetBoardJson() {
120120
void Ml307Board::SetPowerSaveMode(bool enabled) {
121121
// TODO: Implement power save mode for ML307
122122
}
123+
124+
std::string Ml307Board::GetDeviceStatusJson() {
125+
/*
126+
* 返回设备状态JSON
127+
*
128+
* 返回的JSON结构如下:
129+
* {
130+
* "audio_speaker": {
131+
* "volume": 70
132+
* },
133+
* "screen": {
134+
* "brightness": 100,
135+
* "theme": "light"
136+
* },
137+
* "battery": {
138+
* "level": 50,
139+
* "charging": true
140+
* },
141+
* "network": {
142+
* "type": "cellular",
143+
* "carrier": "CHINA MOBILE",
144+
* "csq": 10
145+
* }
146+
* }
147+
*/
148+
auto& board = Board::GetInstance();
149+
auto root = cJSON_CreateObject();
150+
151+
// Audio speaker
152+
auto audio_speaker = cJSON_CreateObject();
153+
auto audio_codec = board.GetAudioCodec();
154+
if (audio_codec) {
155+
cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
156+
}
157+
cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
158+
159+
// Screen brightness
160+
auto backlight = board.GetBacklight();
161+
auto screen = cJSON_CreateObject();
162+
if (backlight) {
163+
cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
164+
}
165+
auto display = board.GetDisplay();
166+
if (display && display->height() > 64) { // For LCD display only
167+
cJSON_AddStringToObject(screen, "theme", display->GetTheme().c_str());
168+
}
169+
cJSON_AddItemToObject(root, "screen", screen);
170+
171+
// Battery
172+
int battery_level = 0;
173+
bool charging = false;
174+
bool discharging = false;
175+
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
176+
cJSON* battery = cJSON_CreateObject();
177+
cJSON_AddNumberToObject(battery, "level", battery_level);
178+
cJSON_AddBoolToObject(battery, "charging", charging);
179+
cJSON_AddItemToObject(root, "battery", battery);
180+
}
181+
182+
// Network
183+
auto network = cJSON_CreateObject();
184+
cJSON_AddStringToObject(network, "type", "cellular");
185+
cJSON_AddStringToObject(network, "carrier", modem_.GetCarrierName().c_str());
186+
int csq = modem_.GetCsq();
187+
if (csq == -1) {
188+
cJSON_AddStringToObject(network, "signal", "unknown");
189+
} else if (csq >= 0 && csq <= 14) {
190+
cJSON_AddStringToObject(network, "signal", "very weak");
191+
} else if (csq >= 15 && csq <= 19) {
192+
cJSON_AddStringToObject(network, "signal", "weak");
193+
} else if (csq >= 20 && csq <= 24) {
194+
cJSON_AddStringToObject(network, "signal", "medium");
195+
} else if (csq >= 25 && csq <= 31) {
196+
cJSON_AddStringToObject(network, "signal", "strong");
197+
}
198+
cJSON_AddItemToObject(root, "network", network);
199+
200+
auto json_str = cJSON_PrintUnformatted(root);
201+
std::string json(json_str);
202+
cJSON_free(json_str);
203+
cJSON_Delete(root);
204+
return json;
205+
}

main/boards/common/ml307_board.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Ml307Board : public Board {
2121
virtual const char* GetNetworkStateIcon() override;
2222
virtual void SetPowerSaveMode(bool enabled) override;
2323
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
24+
virtual std::string GetDeviceStatusJson() override;
2425
};
2526

2627
#endif // ML307_BOARD_H

0 commit comments

Comments
 (0)