Skip to Content

核心概念

深入理解 StreamInd SDK 的架构设计和核心概念。

Signal-Directive 架构

StreamInd SDK 采用创新的 Signal-Directive 双向通信模式,区别于传统的请求-响应模式。

传统模式 vs Signal-Directive

传统模式(Request-Response) ┌────────┐ 请求 ┌────────┐ │ 设备 │ ------> │ 服务器 │ │ │ <------ │ │ └────────┘ 响应 └────────┘ • 同步阻塞 • 强耦合 • 难以扩展 Signal-Directive 模式 ┌────────┐ Signal ┌────────┐ │ 设备 │ ------> │ 平台 │ │ │ │ 认知引擎│ │ │ <------ │ │ └────────┘ Directive└────────┘ • 异步非阻塞 • 松耦合 • 易扩展

Signal(信号)

什么是 Signal?

Signal 是设备向云平台上报的事件、数据或状态。它是设备的”表达”,让平台了解设备的状态和能力。

Signal 的结构

class Signal { std::string uuid; // 唯一标识 std::string type; // 信号类型(如 "sensor.temperature") std::string timestamp; // 时间戳 SignalSource* source; // 信号源信息 SignalPayload* payload; // 有效载荷(数据) };

Signal 示例

1. 传感器数据上报

// 温度传感器数据 foundation::Signal tempSignal("sensor.temperature"); tempSignal.GetPayload()->SetNumber("value", 25.5); tempSignal.GetPayload()->SetString("unit", "celsius"); tempSignal.GetPayload()->SetString("location", "living_room"); sdk.SendSignal(tempSignal);

2. 事件上报

// 按钮按下事件 foundation::Signal buttonSignal("button.pressed"); buttonSignal.GetPayload()->SetString("button_id", "power"); buttonSignal.GetPayload()->SetNumber("press_duration_ms", 1500); sdk.SendSignal(buttonSignal);

3. 状态变更

// 设备状态变更 foundation::Signal stateSignal("device.state_changed"); stateSignal.GetPayload()->SetString("from", "idle"); stateSignal.GetPayload()->SetString("to", "working"); stateSignal.GetPayload()->SetString("reason", "user_command"); sdk.SendSignal(stateSignal);

SignalPayload 数据类型

Payload 支持多种数据类型:

auto* payload = signal.GetPayload(); // 字符串 payload->SetString("key", "value"); // 数字 payload->SetNumber("temperature", 25.5); payload->SetNumber("count", 42); // 布尔值 payload->SetBool("is_active", true); // JSON对象(复杂数据) cJSON* obj = cJSON_CreateObject(); cJSON_AddStringToObject(obj, "status", "ok"); payload->SetObject("metadata", obj);

Directive(指令)

什么是 Directive?

Directive 是云平台向设备下发的控制指令或命令。它是平台的”意图”,告诉设备该做什么。

Directive 的结构

class Directive { std::string id; // 指令ID std::string name; // 指令名称(如 "motor.move") std::string parameters; // 参数(JSON字符串) uint64_t timestamp; // 时间戳 };

Directive 示例

1. 简单控制指令

// LED开关指令 registry.RegisterAction("led.on", [](const auto& directive) { ESP_LOGI(TAG, "Turn on LED"); gpio_set_level(LED_GPIO, 1); return true; // 返回执行结果 });

2. 带参数的指令

// 舵机角度控制 registry.RegisterAction("servo.set_angle", [](const auto& directive) { // 从指令中提取参数 int angle = directive.GetIntParameter("angle", 90); int servo_id = directive.GetIntParameter("servo_id", 0); ESP_LOGI(TAG, "Set servo %d to angle %d", servo_id, angle); // 执行控制 servo_controller->SetAngle(servo_id, angle); return true; });

3. 异步任务指令

// 电机移动指令(耗时操作) registry.RegisterAction("motor.move", [&sdk](const auto& directive) { int distance = directive.GetIntParameter("distance_cm", 100); int speed = directive.GetIntParameter("speed", 100); // 使用异步任务管理 std::string task_id = sdk.ExecuteHardwareTask( "motor.move", "motor", // 硬件类型 [distance, speed]() -> bool { // 执行移动(可能需要几秒钟) motor_controller->Move(distance, speed); vTaskDelay(pdMS_TO_TICKS(2000)); return true; }, [](const std::string& id, bool success) { // 完成回调 ESP_LOGI(TAG, "Motor task %s: %s", id.c_str(), success ? "SUCCESS" : "FAILED"); } ); return !task_id.empty(); });

参数提取方法

// 字符串参数 std::string direction = directive.GetStringParameter("direction", "forward"); // 整数参数 int angle = directive.GetIntParameter("angle", 90); // 浮点数参数 double speed = directive.GetDoubleParameter("speed", 1.0); // 布尔参数 bool enabled = directive.GetBoolParameter("enabled", true);

Capability(能力)

能力注册系统

设备通过 CapabilityRegistry 向平台声明自己的能力。这包括:

  1. 动作能力(Actions) - 设备能执行哪些指令
  2. 数据能力(Data) - 设备能提供哪些数据

动作注册

auto& registry = sdk.GetRegistry(); // 注册单个动作 registry.RegisterAction("action_name", handler_function); // 查看已注册的能力 auto stats = registry.GetStats(); ESP_LOGI(TAG, "Registered: %d actions, %d data sources", stats.action_count, stats.data_provider_count);

数据注册

数据注册支持三种模式:

1. 事件触发模式

// 当特定事件发生时上报数据 registry.RegisterEventData( "battery.status", // 数据类型 []() -> cJSON* { // 数据提供器 cJSON* data = cJSON_CreateObject(); cJSON_AddNumberToObject(data, "level", battery_get_level()); cJSON_AddBoolToObject(data, "charging", is_charging()); return data; }, "battery.low" // 触发事件 ); // 触发数据上报 registry.TriggerEventReport("battery.low");

2. 定时上报模式

// 每10秒自动上报一次 registry.RegisterTimerData( "system.stats", []() -> cJSON* { cJSON* data = cJSON_CreateObject(); cJSON_AddNumberToObject(data, "free_heap", esp_get_free_heap_size()); cJSON_AddNumberToObject(data, "uptime", esp_timer_get_time() / 1000000); return data; }, 10000 // 10秒间隔 );

3. 实时流模式

// 实时数据流(如传感器连续读取) registry.RegisterRealTimeData( "sensor.distance", []() -> cJSON* { cJSON* data = cJSON_CreateObject(); cJSON_AddNumberToObject(data, "distance_cm", ultrasonic_read()); return data; } );

硬件适配器模式

设计理念

StreamInd SDK 推荐使用 硬件适配器(Hardware Adapter) 模式来组织代码:

  • 适配器层 - 负责能力注册和指令转发(轻量级)
  • 硬件层 - 负责硬件生命周期和实际控制(由应用管理)

适配器模板

class MyHardwareAdapter { public: // 返回模块名称 static std::string GetModuleName() { return "my_hardware"; } // 注册能力 static void RegisterCapabilities(streamind::CapabilityRegistry& registry) { // 注册动作 registry.RegisterAction("my_hardware.command", HandleCommand); // 注册数据 registry.RegisterEventData("my_hardware.status", GetStatus, "status_changed"); } private: // 指令处理器 static bool HandleCommand(const streamind::foundation::Directive& directive) { // 获取硬件实例(由应用管理) auto* hardware = Application::GetHardware(); // 执行操作 int param = directive.GetIntParameter("param", 0); hardware->DoSomething(param); return true; } // 数据提供器 static cJSON* GetStatus() { auto* hardware = Application::GetHardware(); cJSON* data = cJSON_CreateObject(); cJSON_AddStringToObject(data, "status", hardware->GetStatus()); return data; } };

注册适配器

// 在app_main中 auto& registry = sdk.GetRegistry(); // 使用模板注册 streamind::RegisterHardwareAdapter<MyHardwareAdapter>(registry); streamind::RegisterHardwareAdapter<AudioAdapter>(registry); streamind::RegisterHardwareAdapter<MotorAdapter>(registry); // 初始化所有能力 sdk.InitializeCapabilities();

异步任务管理

为什么需要异步任务?

某些硬件操作需要较长时间(如电机移动、音频播放),如果在指令处理器中同步执行:

  • ❌ 阻塞主线程
  • ❌ 导致超时
  • ❌ 无法并发控制多个硬件

ExecuteHardwareTask

StreamInd SDK 提供内置的异步任务管理:

std::string task_id = sdk.ExecuteHardwareTask( "task_name", // 任务名称 "hardware_type", // 硬件类型(同类型硬件会排队) []() -> bool { // 任务函数 // 执行耗时操作 do_long_running_work(); return true; // 返回执行结果 }, [](const std::string& id, bool success) { // 完成回调(可选) if (success) { ESP_LOGI(TAG, "Task %s completed", id.c_str()); // 上报结果Signal } } );

任务特性

  • 硬件排队 - 同类型硬件的任务会自动排队执行
  • 任务ID - 每个任务都有唯一ID,可用于追踪
  • 中止任务 - 支持中途取消任务
// 中止特定硬件类型的所有任务 sdk.AbortHardwareTasks("motor"); // 中止特定任务 sdk.AbortTask(task_id); // 检查是否有硬件忙碌 bool busy = sdk.IsAnyHardwareBusy();

连接管理

自动重连

SDK 内置自动重连机制:

streamind::Config config; config.reconnect_interval_ms = 5000; // 5秒重连间隔 config.connection_timeout_ms = 10000; // 10秒连接超时

连接状态回调

sdk.SetConnectionCallback([&sdk](bool connected, const std::string& msg) { if (connected) { ESP_LOGI(TAG, "✅ Connected"); // 连接成功后的操作 sdk.StartDirectiveReceiving(); // 可选:重新注册设备能力 sdk.InitializeCapabilities(); } else { ESP_LOGW(TAG, "❌ Disconnected: %s", msg.c_str()); // 连接断开后的操作 sdk.StopDirectiveReceiving(); } });

TraceID 机制

什么是 TraceID?

TraceID 用于关联一次完整的交互流程(如一轮语音对话)。

使用场景

// 1. 生成TraceID std::string trace_id = sdk.GenerateTraceId(); // 2. 在Signal中设置TraceID foundation::Signal signal("user.speech_start"); signal.SetSourceTraceId(trace_id); sdk.SendSignal(signal); // 3. 接收的Directive会携带相同的TraceID // 平台根据TraceID关联请求和响应

最佳实践

1. 合理组织代码

your_project/ ├── main/ │ ├── core/ # 核心应用逻辑 │ ├── hardware/ # 硬件驱动 │ ├── integrations/ # StreamInd适配器 │ └── main.cpp └── components/ └── streamind_sdk/ # SDK

2. 错误处理

auto err = sdk.SendSignal(signal); if (err != streamind::Error::OK) { ESP_LOGE(TAG, "Failed to send signal: %s", sdk.GetLastError().c_str()); // 记录到本地或重试 }

3. 资源管理

// Signal和Directive中的cJSON对象会自动释放 // 但如果你手动创建cJSON,需要手动释放 cJSON* custom_obj = cJSON_CreateObject(); // ... 使用 ... cJSON_Delete(custom_obj); // 手动释放

4. 日志级别

// 生产环境 esp_log_level_set("StreamInd", ESP_LOG_INFO); // 开发调试 esp_log_level_set("StreamInd", ESP_LOG_DEBUG); esp_log_level_set("WebSocketClient", ESP_LOG_DEBUG);

下一步

Last updated on