12浏览
查看: 12|回复: 0

[K10项目分享] 行空板k10小智物联——学习状态辅助机(撰写中)

[复制链接]
本帖最后由 云天 于 2026-1-22 22:37 编辑

【项目背景】
本项目基于行空板 K10 硬件平台,通过修改小智 AI 开源固件,融合 Easy IOT 物联网平台、I2C 超声波传感器(URM09)和 360 舵机,打造一套儿童学习姿态智能看护系统。系统可实现远程唤醒姿态监测、实时距离检测、语音提醒、物联网消息互通等核心功能,帮助家长远程监管孩子的学习状态,及时纠正不良坐姿,同时支持自定义语音交互(如讲故事、播报健康知识)。
【核心功能】
物联网双向通信:小智 AI 与 Easy IOT 平台互发指令,支持远程开启 / 关闭监测、发送语音交互指令(如 “讲个故事”“播报坐姿重要性”);姿态监测:通过 I2C 超声波传感器实时检测孩子与学习桌的距离,判断是否趴桌学习,触发语音提醒并上报数据至物联网平台;硬件控制:支持通过语音 / 远程指令控制行空板 P0 引脚的 360 舵机(转速 / 方向),扩展互动功能;语音交互:小智 AI 可根据物联网平台指令或本地检测结果,通过自然语言提醒孩子端正坐姿、播报健康知识等。
【硬件清单】
行空板k10小智物联——学习状态辅助机(撰写中)图1
【软件环境】
固件基础:小智 AI 开源代码(xiaozhi-esp32-2.0.5);开发框架:ESP-IDF(适配 ESP32 的物联网开发框架);物联网平台:Easy IOT(DFRobot 提供的 MQTT 物联网平台);传感器库:适配 ESP-IDF 的 URM09 超声波传感器 C++ 库(基于 Arduino 库改写)
【核心技术实现】
1.系统整体架构
行空板k10小智物联——学习状态辅助机(撰写中)图2
2.关键功能实现
(1)Easy IOT MQTT 通信集成
核心修改文件
xiaozhi-esp32-2.0.5/main/boards/common/board.h:新增 MQTT 通信虚函数接口;
  1. // +++ 新增的 Easy IoT 相关接口 +++
  2.     virtual void StartEasyIoTService() {}  // 默认空实现(可选)
  3.     virtual bool IsEasyIoTConnected() const { return false; }  // 默认返回false
  4.     virtual void SendEasyIoTSensorData(int volume) {}  // 示例方法
  5.     virtual void SendEasyIoTStringData(const std::string& data, const std::string& data_type){}
复制代码
xiaozhi-esp32-2.0.5/main/boards/df-k10/df_k10_board.cc:实现 MQTT 客户端初始化、事件处理、消息收发;
  1. #include "mqtt_client.h"
  2. #define IOT_TOPIC "z4ksqL6Ig"          // 设备数据上报主题
  3. #define IOT_TOPIC_control "PufUUHKNR"  // 控制指令接收主题  
  4. #define IOT_TOPIC_talk "6zYbX90Hg"     // 设备对话通信主题
复制代码
在 class Df_K10Board : public WifiBoard 的 private 区域 新增:
  1. private:
  2.     // ... 原有代码 ...
  3.    
  4.     // ===================== 新增MQTT相关成员 =====================
  5.     // MQTT客户端实例句柄,管理MQTT连接
  6.     esp_mqtt_client_handle_t easyiot_mqtt_client_ = nullptr;
  7.    
  8.     // MQTT连接状态标志,true表示已连接
  9.     bool easyiot_connected_ = false;
  10.    
  11.     // MQTT消息回调函数类型定义,用于处理特定主题的消息
  12.     using MqttMessageCallback = std::function<void(const std::string& topic, const std::string& payload)>;
  13.    
  14.     // 主题-回调函数映射表,存储不同主题对应的处理函数
  15.     std::unordered_map<std::string, MqttMessageCallback> topic_callbacks_;
  16.    
  17.     // MQTT事件处理静态函数(适配ESP32事件系统)
  18.     static void MqttEventHandler(void* handler_args, esp_event_base_t base,
  19.                                  int32_t event_id, void* event_data);
  20.    
  21.     // MQTT事件处理成员函数(实际处理逻辑)
  22.     void HandleMqttEvent(esp_mqtt_event_handle_t event);
  23.    
  24.     // MQTT消息处理核心函数,分发到具体回调
  25.     void ProcessMqttMessage(const std::string& topic, const std::string& payload);
  26.    
  27.     // 订阅指定主题,可设置服务质量等级
  28.     esp_err_t SubscribeToTopic(const std::string& topic, int qos = 0);
  29.    
  30.     // 取消订阅指定主题
  31.     esp_err_t UnsubscribeFromTopic(const std::string& topic);
  32.    
  33.     // ... 其他原有成员 ...
复制代码
在 class Df_K10Board : public WifiBoard 的 public 区域 新增:
  1. public:
  2.     // ... 原有代码 ...
  3.    
  4.     // ===================== 新增MQTT相关公共接口 =====================
  5.     // 启动Easy IoT服务,初始化MQTT客户端并建立连接
  6.     void StartEasyIoTService() override;
  7.    
  8.     // 检查MQTT连接状态
  9.     bool IsEasyIoTConnected() const override;
  10.    
  11.     // 发送传感器数据到MQTT主题
  12.     void SendEasyIoTSensorData(int volume) override;
  13.    
  14.     // 发送字符串数据到MQTT主题,支持数据类型标识
  15.     void SendEasyIoTStringData(const std::string& data, const std::string& data_type) override;
  16.    
  17.     // 注册主题回调函数:为指定主题绑定消息处理函数
  18.     void RegisterTopicCallback(const std::string& topic, MqttMessageCallback callback);
  19.    
  20.     // 移除主题回调函数:取消指定主题的消息处理绑定
  21.     void RemoveTopicCallback(const std::string& topic);
  22.    
  23.     // ... 其他原有方法 ...
复制代码
Df_K10Board 类中 MQTT 相关成员函数的完整实现代码
  1. // ===================== MQTT相关成员函数实现 =====================
  2. /**
  3. * 启动Easy IoT服务
  4. * 功能:初始化MQTT客户端,配置连接参数,注册事件处理器,并启动连接
  5. * 注意:只能启动一次,重复调用会警告并返回
  6. */
  7. void Df_K10Board::StartEasyIoTService() {
  8.     if (easyiot_mqtt_client_ != nullptr) {
  9.         ESP_LOGW(TAG, "Easy IoT service already started");
  10.         return;
  11.     }
  12.     ESP_LOGI(TAG, "Starting Easy IoT service");
  13.    
  14.     // 配置 MQTT 客户端
  15.     esp_mqtt_client_config_t mqtt_cfg = {};
  16.     mqtt_cfg.broker.address.uri = "mqtt://iot.dfrobot.com.cn:1883";  // MQTT服务器地址
  17.     mqtt_cfg.credentials.client_id = "AVNLqL6SR";  // 客户端ID
  18.     mqtt_cfg.credentials.username = "AVNLqL6SR";   // 用户名
  19.     mqtt_cfg.credentials.authentication.password = "04HYqY6IRz";  // 密码
  20.    
  21.     // 配置连接参数
  22.     mqtt_cfg.session.keepalive = 60;  // 60秒心跳包
  23.     mqtt_cfg.session.disable_clean_session = false;  // 清除会话(重新连接时不保留旧消息)
  24.     mqtt_cfg.network.disable_auto_reconnect = false;  // 启用自动重连
  25.     mqtt_cfg.network.reconnect_timeout_ms = 5000;  // 5秒重连间隔
  26.    
  27.     // 初始化MQTT客户端
  28.     easyiot_mqtt_client_ = esp_mqtt_client_init(&mqtt_cfg);
  29.    
  30.     // 注册事件处理函数
  31.     esp_mqtt_client_register_event(easyiot_mqtt_client_,
  32.         static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID),  // 监听所有MQTT事件
  33.         MqttEventHandler,  // 静态事件处理函数
  34.         this);  // 传入当前对象指针作为上下文
  35.    
  36.     // 启动MQTT客户端(开始连接服务器)
  37.     esp_mqtt_client_start(easyiot_mqtt_client_);
  38.    
  39.     // ===================== 注册默认回调函数 =====================
  40.    
  41.     // 1. 控制主题回调:处理LED控制命令
  42.     RegisterTopicCallback(IOT_TOPIC_control, [this](const std::string& topic, const std::string& payload) {
  43.         // 处理LED控制命令
  44.         if (payload == "on") {
  45.             led_strip_->SetAllColor(RGBToColor(0, 255, 0));  // 绿色
  46.             GetDisplay()->ShowNotification("LED ON");
  47.         } else if (payload == "off") {
  48.             led_strip_->SetAllColor(RGBToColor(0, 0, 0));  // 关闭
  49.             GetDisplay()->ShowNotification("LED OFF");
  50.         } else if (payload == "blink") {
  51.             // 闪烁效果
  52.             for (int i = 0; i < 5; i++) {
  53.                 led_strip_->SetAllColor(RGBToColor(255, 255, 0));  // 黄色
  54.                 vTaskDelay(pdMS_TO_TICKS(200));
  55.                 led_strip_->SetAllColor(RGBToColor(0, 0, 0));  // 关闭
  56.                 vTaskDelay(pdMS_TO_TICKS(200));
  57.             }
  58.         }
  59.     });
  60.    
  61.     // 2. 对话主题回调:处理语音交互命令
  62.     RegisterTopicCallback(IOT_TOPIC_talk, [this](const std::string& topic, const std::string& payload) {
  63.         auto& app = Application::GetInstance();
  64.         DeviceState state = app.GetDeviceState();
  65.         
  66.         // 在屏幕上显示收到的消息
  67.         GetDisplay()->ShowNotification(payload);
  68.         
  69.         // 根据设备状态处理消息
  70.         if (state == kDeviceStateListening) {
  71.             // 向服务器发送唤醒词检测
  72.             app.NotifyWakeWord(payload);
  73.         } else if (state == kDeviceStateIdle) {
  74.             // 触发完整的对话
  75.             app.WakeWordInvoke(payload);
  76.         } else if (state == kDeviceStateSpeaking) {
  77.             if (payload == "stop") {
  78.                 // 停止说话命令
  79.                 app.StopSpeaking();
  80.             }
  81.         }
  82.     });
  83. }
  84. /**
  85. * 检查MQTT连接状态
  86. * 返回值:true表示已连接,false表示未连接
  87. */
  88. bool Df_K10Board::IsEasyIoTConnected() const {
  89.     return easyiot_connected_;
  90. }
  91. /**
  92. * 发送字符串数据到Easy IoT平台
  93. * 功能:将字符串数据(如base64编码的图片)以JSON格式发送到指定主题
  94. * 参数:data - 要发送的数据字符串
  95. *        data_type - 数据类型标识(如"Information"、"camera_photo"等)
  96. * 注意:会自动处理数据大小限制,过大数据会被截断
  97. */
  98. void Df_K10Board::SendEasyIoTStringData(const std::string& data, const std::string& data_type) {
  99.     if (!easyiot_mqtt_client_) {
  100.         ESP_LOGW(TAG, "Easy IoT MQTT client not initialized");
  101.         return;
  102.     }
  103.    
  104.     // MQTT消息大小限制(假设最大为4KB)
  105.     size_t json_overhead = 100;  // JSON格式额外开销
  106.     size_t max_mqtt_payload = 4096;  // 假设MQTT最大payload为4KB
  107.    
  108.     ESP_LOGW(TAG, "Sending data type: %s, data length: %u", data_type.c_str(), data.length());
  109.    
  110.     // 检查数据是否超过MQTT限制
  111.     if (data.length() + json_overhead > max_mqtt_payload) {
  112.         // 数据太大,需要截断
  113.         ESP_LOGW(TAG, "Information data too large (%u bytes), truncating...", data.length());
  114.         
  115.         // 计算最大可发送数据大小
  116.         size_t max_data_size = max_mqtt_payload - json_overhead - 100;  // 留出更多空间
  117.         if (max_data_size > data.length()) {
  118.             max_data_size = data.length();
  119.         }
  120.         
  121.         // 安全地截取子字符串
  122.         std::string truncated_data = data.substr(0, max_data_size);
  123.         
  124.         // 确保数据中不包含null字符(替换为空格)
  125.         for (size_t i = 0; i < truncated_data.length(); i++) {
  126.             if (truncated_data[i] == '\0') {
  127.                 truncated_data[i] = ' ';
  128.             }
  129.         }
  130.         
  131.         // 构建JSON格式的payload
  132.         char payload[max_mqtt_payload];
  133.         int written = snprintf(payload, sizeof(payload),
  134.                                "{"type":"%s","data":"%s"}",
  135.                                data_type.c_str(), truncated_data.c_str());
  136.         
  137.         if (written < 0 || written >= (int)sizeof(payload)) {
  138.             ESP_LOGE(TAG, "Failed to format JSON payload");
  139.             return;
  140.         }
  141.         
  142.         // 发布消息到主题
  143.         int msg_id = esp_mqtt_client_publish(easyiot_mqtt_client_,
  144.                                             IOT_TOPIC,  // 设备数据上报主题
  145.                                             payload, 0, 1, 0);
  146.         ESP_LOGI(TAG, "Sent truncated data to Easy IoT, msg_id=%d, payload_size=%d",
  147.                 msg_id, written);
  148.         return;
  149.     }
  150.    
  151.     // 数据大小合适,直接发送
  152.     char payload[data.length() + json_overhead + 100];
  153.    
  154.     // 清理数据中的null字符
  155.     std::string clean_data = data;
  156.     for (size_t i = 0; i < clean_data.length(); i++) {
  157.         if (clean_data[i] == '\0') {
  158.             clean_data[i] = ' ';
  159.         }
  160.     }
  161.    
  162.     // 根据数据类型构建不同的JSON格式
  163.     int written = 0;
  164.     if (data_type == "Information" || data_type == "camera_photo") {
  165.         // 特殊数据类型包含大小信息
  166.         written = snprintf(payload, sizeof(payload),
  167.                 "{"type":"%s","data":"%s","size":%u}",
  168.                 data_type.c_str(),
  169.                 clean_data.c_str(),
  170.                 clean_data.length());
  171.     } else {
  172.         // 普通数据类型
  173.         written = snprintf(payload, sizeof(payload),
  174.                 "{"type":"%s","data":"%s"}",
  175.                 data_type.c_str(), clean_data.c_str());
  176.     }
  177.    
  178.     if (written < 0 || written >= (int)sizeof(payload)) {
  179.         ESP_LOGE(TAG, "Failed to format JSON payload");
  180.         return;
  181.     }
  182.    
  183.     // 发布消息
  184.     int msg_id = esp_mqtt_client_publish(easyiot_mqtt_client_,
  185.                                         IOT_TOPIC,  // 设备数据上报主题
  186.                                         payload, 0, 1, 0);
  187.     ESP_LOGI(TAG, "Sent string data to Easy IoT, msg_id=%d, payload_size=%d",
  188.             msg_id, written);
  189. }
  190. /**
  191. * 发送传感器数据到Easy IoT平台
  192. * 功能:将传感器数据(如音量)以JSON格式发送
  193. * 参数:volume - 传感器采集的音量值
  194. */
  195. void Df_K10Board::SendEasyIoTSensorData(int volume) {
  196.     // 构建JSON格式的传感器数据
  197.     char payload[100];
  198.     snprintf(payload, sizeof(payload),
  199.              "{"volume":%d}",
  200.              volume);
  201.    
  202.     // 发布消息到传感器数据主题
  203.     int msg_id = esp_mqtt_client_publish(easyiot_mqtt_client_,
  204.                                          IOT_TOPIC,  // 设备数据上报主题
  205.                                          payload, 0, 1, 0);
  206.     ESP_LOGI(TAG, "Sent sensor data to Easy IoT, msg_id=%d", msg_id);
  207. }
  208. /**
  209. * 静态MQTT事件处理函数
  210. * 功能:将ESP32事件系统的MQTT事件转发给成员函数处理
  211. * 参数:handler_args - 传入的上下文(this指针)
  212. *        base - 事件基础类型
  213. *        event_id - 事件ID
  214. *        event_data - 事件数据
  215. */
  216. void Df_K10Board::MqttEventHandler(void* handler_args, esp_event_base_t base,
  217.                                   int32_t event_id, void* event_data) {
  218.     // 将静态上下文转换为对象指针,调用成员函数处理事件
  219.     auto self = static_cast<Df_K10Board*>(handler_args);
  220.     self->HandleMqttEvent(static_cast<esp_mqtt_event_handle_t>(event_data));
  221. }
  222. /**
  223. * 实例MQTT事件处理函数
  224. * 功能:处理所有MQTT事件,包括连接、断开、订阅、发布、数据接收等
  225. * 参数:event - MQTT事件句柄
  226. */
  227. void Df_K10Board::HandleMqttEvent(esp_mqtt_event_handle_t event) {
  228.     switch (event->event_id) {
  229.         case MQTT_EVENT_CONNECTED:
  230.             ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
  231.             easyiot_connected_ = true;  // 更新连接状态
  232.             
  233.             // 连接成功后自动订阅主题
  234.             SubscribeToTopic(IOT_TOPIC_control);  // 控制主题
  235.             SubscribeToTopic(IOT_TOPIC_talk);     // 对话主题
  236.             
  237.             // 在屏幕上显示连接成功通知
  238.             GetDisplay()->ShowNotification("MQTT Connected");
  239.             break;
  240.             
  241.         case MQTT_EVENT_DISCONNECTED:
  242.             ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
  243.             easyiot_connected_ = false;  // 更新连接状态
  244.             GetDisplay()->ShowNotification("MQTT Disconnected");
  245.             break;
  246.             
  247.         case MQTT_EVENT_SUBSCRIBED:
  248.             ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
  249.             break;
  250.             
  251.         case MQTT_EVENT_UNSUBSCRIBED:
  252.             ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
  253.             break;
  254.             
  255.         case MQTT_EVENT_PUBLISHED:
  256.             ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
  257.             break;
  258.             
  259.         case MQTT_EVENT_DATA: {
  260.             ESP_LOGI(TAG, "MQTT_EVENT_DATA");
  261.             
  262.             // 提取主题和消息内容
  263.             std::string topic(event->topic, event->topic_len);
  264.             std::string payload(event->data, event->data_len);
  265.             
  266.             ESP_LOGI(TAG, "Received message on topic: %s", topic.c_str());
  267.             ESP_LOGI(TAG, "Payload: %s", payload.c_str());
  268.             
  269.             // 处理接收到的消息
  270.             ProcessMqttMessage(topic, payload);
  271.             break;
  272.         }
  273.             
  274.         case MQTT_EVENT_ERROR:
  275.             ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
  276.             // 处理TCP传输错误
  277.             if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
  278.                 ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x",
  279.                         event->error_handle->esp_tls_last_esp_err);
  280.                 ESP_LOGE(TAG, "Last tls stack error number: 0x%x",
  281.                         event->error_handle->esp_tls_stack_err);
  282.             }
  283.             break;
  284.             
  285.         default:
  286.             ESP_LOGI(TAG, "Other event id:%d", event->event_id);
  287.             break;
  288.     }
  289. }
  290. /**
  291. * 处理MQTT消息
  292. * 功能:根据主题分发消息到对应的回调函数
  293. * 参数:topic - 消息主题
  294. *        payload - 消息内容
  295. */
  296. void Df_K10Board::ProcessMqttMessage(const std::string& topic, const std::string& payload) {
  297.     // 在屏幕上显示简短通知
  298.     GetDisplay()->ShowNotification("MQTT Msg: " + payload.substr(0, 20));
  299.    
  300.     // 查找是否有注册的回调函数
  301.     auto it = topic_callbacks_.find(topic);
  302.     if (it != topic_callbacks_.end()) {
  303.         // 调用注册的回调函数
  304.         it->second(topic, payload);
  305.     } else {
  306.         // 默认消息处理逻辑(无回调注册时使用)
  307.         ESP_LOGW(TAG, "No callback registered for topic: %s", topic.c_str());
  308.         
  309.         // 简单解析控制命令(示例)
  310.         if (topic.find(IOT_TOPIC_control) != std::string::npos) {
  311.             if (payload.find(""led":"on"") != std::string::npos) {
  312.                 led_strip_->SetAllColor(RGBToColor(255, 0, 0));  // 红色
  313.             } else if (payload.find(""led":"off"") != std::string::npos) {
  314.                 led_strip_->SetAllColor(RGBToColor(0, 0, 0));  // 关闭
  315.             }
  316.         }
  317.     }
  318. }
  319. /**
  320. * 注册主题回调函数
  321. * 功能:为指定主题绑定消息处理函数
  322. * 参数:topic - 主题名称
  323. *        callback - 回调函数
  324. */
  325. void Df_K10Board::RegisterTopicCallback(const std::string& topic, MqttMessageCallback callback) {
  326.     topic_callbacks_[topic] = callback;
  327.    
  328.     // 如果已经连接,立即订阅该主题
  329.     if (easyiot_connected_ && easyiot_mqtt_client_) {
  330.         SubscribeToTopic(topic);
  331.     }
  332. }
  333. /**
  334. * 移除主题回调函数
  335. * 功能:取消指定主题的回调函数绑定
  336. * 参数:topic - 主题名称
  337. */
  338. void Df_K10Board::RemoveTopicCallback(const std::string& topic) {
  339.     topic_callbacks_.erase(topic);
  340. }
  341. /**
  342. * 订阅主题
  343. * 功能:向MQTT服务器订阅指定主题
  344. * 参数:topic - 主题名称
  345. *        qos - 服务质量等级(0-2,默认0)
  346. * 返回值:ESP_OK成功,ESP_FAIL失败
  347. */
  348. esp_err_t Df_K10Board::SubscribeToTopic(const std::string& topic, int qos) {
  349.     if (!easyiot_mqtt_client_ || !easyiot_connected_) {
  350.         ESP_LOGW(TAG, "MQTT client not connected, cannot subscribe to topic: %s", topic.c_str());
  351.         return ESP_FAIL;
  352.     }
  353.    
  354.     int msg_id = esp_mqtt_client_subscribe(easyiot_mqtt_client_, topic.c_str(), qos);
  355.     if (msg_id < 0) {
  356.         ESP_LOGE(TAG, "Failed to subscribe to topic: %s", topic.c_str());
  357.         return ESP_FAIL;
  358.     }
  359.    
  360.     ESP_LOGI(TAG, "Subscribed to topic: %s, msg_id: %d", topic.c_str(), msg_id);
  361.     return ESP_OK;
  362. }
  363. /**
  364. * 取消订阅主题
  365. * 功能:从MQTT服务器取消订阅指定主题
  366. * 参数:topic - 主题名称
  367. * 返回值:ESP_OK成功,ESP_FAIL失败
  368. */
  369. esp_err_t Df_K10Board::UnsubscribeFromTopic(const std::string& topic) {
  370.     if (!easyiot_mqtt_client_ || !easyiot_connected_) {
  371.         return ESP_FAIL;
  372.     }
  373.    
  374.     int msg_id = esp_mqtt_client_unsubscribe(easyiot_mqtt_client_, topic.c_str());
  375.     if (msg_id < 0) {
  376.         ESP_LOGE(TAG, "Failed to unsubscribe from topic: %s", topic.c_str());
  377.         return ESP_FAIL;
  378.     }
  379.    
  380.     ESP_LOGI(TAG, "Unsubscribed from topic: %s, msg_id: %d", topic.c_str(), msg_id);
  381.     return ESP_OK;
  382. }
复制代码
mcp_server.cc 文件中新增 MCP 工具函数,用于将信息发送到物联网平台:
  1. // ===================== 新增:发送信息到物联网平台工具 =====================
  2. /**
  3. * 工具名称:self.send_data_to_iot
  4. * 功能描述:发送信息到物联网平台
  5. * 适用场景:当需要与物联网平台共享信息或照片时使用此工具
  6. * 参数说明:
  7. *   - Information: 要发送的字符串信息,可以是文本信息
  8. * 调用示例:用户说"分享***信息到物联网"
  9. */
  10. AddTool("self.send_data_to_iot",
  11.     "发送信息到物联网平台,Information:信息",
  12.     PropertyList({
  13.                 Property("Information", kPropertyTypeString)
  14.             }),
  15.     [camera, &board](const PropertyList& properties) -> ReturnValue {
  16.       // 从参数中提取要发送的信息
  17.       auto Information = properties["Information"].value<std::string>();
  18.       
  19.       // 记录调试信息
  20.       ESP_LOGI("MCP_SERVER", "Sending data to IoT platform, length: %d", Information.length());
  21.       
  22.       // 发送到物联网平台,数据类型为"Information"
  23.       board.SendEasyIoTStringData(std::string(Information), "Information");
  24.       
  25.       // 返回成功
  26.       return true;
  27.     });
复制代码
核心代码逻辑:

  初始化 MQTT 客户端,连接 Easy IOT 平台(地址:mqtt://iot.dfrobot.com.cn:1883);
  订阅两个核心主题:
    IOT_TOPIC_control:硬件控制指令(如开关灯、舵机控制);
    IOT_TOPIC_talk:语音交互指令(如 “讲个故事”“播报坐姿重要性”);
  实现消息回调机制:接收到平台指令后,触发硬件控制或语音交互;
  封装数据上报函数:支持字符串 / 传感器数据(如距离、提醒信息)格式化上报至平台。
【URM09 超声波传感器适配】
核心修改:
  • 将 Arduino 版 URM09 库改写为 ESP-IDF 兼容的 C++ 库(DFRobot_URM09.h/.cc),适配 I2C 主机驱动;
  • 在df_k10_board.cc中集成传感器初始化、数据读取函数:
    • InitializeSensors():初始化 I2C 总线并挂载传感器,设置自动测量模式(500cm 量程);
    • GetSensorData():读取距离 / 温度数据,格式化返回(如 “当前距离为 20 厘米,环境温度为 25.5 摄氏度”);
  • 在mcp_server.cc注册self.get_distance工具函数,支持小智 AI 调用传感器数据。

URM09-I2C超声波测距传感器库DFRobot_URM09.h文件代码内容
  1. /*!
  2. * @file DFRobot_URM09.h
  3. * @brief Basic structure of DFRobot_URM09 class for ESP-IDF
  4. * @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
  5. * @license The MIT License (MIT)
  6. * @author ZhixinLiu(zhixin.liu@dfrobot.com)
  7. * @version V2.0 (ESP-IDF Adaptation)
  8. * @date 2024-01-01
  9. * @url https://github.com/DFRobot/DFRobot_URM09
  10. */
  11. #ifndef __DFRobot_URM09_H__
  12. #define __DFRobot_URM09_H__
  13. #include <driver/i2c_master.h>
  14. #include <esp_log.h>
  15. #include <cstdint>
  16. #include <string>
  17. // 测量模式定义
  18. #define MEASURE_MODE_AUTOMATIC  0x80
  19. #define MEASURE_MODE_PASSIVE    0x00
  20. #define CMD_DISTANCE_MEASURE    0x01
  21. // 测量范围定义
  22. #define MEASURE_RANG_500        0x20
  23. #define MEASURE_RANG_300        0x10
  24. #define MEASURE_RANG_150        0x00
  25. class DFRobot_URM09 {
  26. public:
  27.     // 寄存器枚举
  28.     typedef enum {
  29.         eSLAVEADDR_INDEX = 0,
  30.         ePID_INDEX,
  31.         eVERSION_INDEX,
  32.         eDIST_H_INDEX,
  33.         eDIST_L_INDEX,
  34.         eTEMP_H_INDEX,
  35.         eTEMP_L_INDEX,
  36.         eCFG_INDEX,
  37.         eCMD_INDEX,
  38.         eREG_NUM
  39.     } eRegister_t;
  40.     /**
  41.      * @brief 构造函数
  42.      * @param i2c_bus I2C总线句柄(已在主程序中初始化)
  43.      * @param i2c_addr I2C设备地址(默认为0x11)
  44.      */
  45.     DFRobot_URM09(i2c_master_bus_handle_t i2c_bus, uint8_t i2c_addr = 0x11);
  46.    
  47.     /**
  48.      * @brief 析构函数
  49.      */
  50.     ~DFRobot_URM09();
  51.     /**
  52.      * @brief 初始化传感器
  53.      * @return 初始化状态
  54.      */
  55.     bool begin();
  56.     /**
  57.      * @brief 设置测量模式和范围
  58.      * @param range 测量范围
  59.      * @param mode 测量模式
  60.      */
  61.     void setModeRange(uint8_t range, uint8_t mode);
  62.     /**
  63.      * @brief 发送测量命令(被动模式)
  64.      */
  65.     void measurement();
  66.     /**
  67.      * @brief 获取温度值
  68.      * @return 温度值(℃)
  69.      */
  70.     float getTemperature();
  71.     /**
  72.      * @brief 获取距离值
  73.      * @return 距离值(cm)
  74.      */
  75.     int16_t getDistance();
  76.     /**
  77.      * @brief 扫描I2C设备
  78.      * @return 设备地址,未找到返回-1
  79.      */
  80.     int16_t scanDevice();
  81.     /**
  82.      * @brief 获取当前I2C地址
  83.      * @return I2C地址
  84.      */
  85.     uint8_t getI2CAddress();
  86.     /**
  87.      * @brief 修改I2C地址
  88.      * @param address 新地址(1-127)
  89.      */
  90.     void modifyI2CAddress(uint8_t address);
  91. private:
  92.     // I2C写函数
  93.     esp_err_t i2cWriteRegister(uint8_t reg, uint8_t *data, uint8_t len);
  94.    
  95.     // I2C读函数
  96.     esp_err_t i2cReadRegister(uint8_t reg, uint8_t *data, uint8_t len);
  97.     i2c_master_bus_handle_t i2c_bus_;      // I2C总线句柄
  98.     i2c_master_dev_handle_t i2c_dev_;      // I2C设备句柄
  99.     uint8_t i2c_addr_;                     // I2C设备地址
  100.     uint8_t txbuf[10] = {0};               // 发送缓冲区
  101.     const char* TAG = "URM09";             // 日志标签
  102. };
  103. #endif // __DFRobot_URM09_H__
复制代码

URM09-I2C超声波测距传感器库文件DFRobot_URM09.cc文件内容
  1. /*!
  2. * @file DFRobot_URM09.cpp
  3. * @brief ESP-IDF implementation of DFRobot_URM09 class
  4. * @copyright Copyright (c) 2010 DFRobot Co.Ltd ([url]http://www.dfrobot.com[/url])
  5. * @license The MIT License (MIT)
  6. * @author ZhixinLiu([email]zhixin.liu@dfrobot.com[/email])
  7. * @version V2.0 (ESP-IDF Adaptation)
  8. * @date 2024-01-01
  9. */
  10. #include "DFRobot_URM09.h"
  11. // 构造函数 - 注意初始化列表顺序必须与类声明顺序一致
  12. DFRobot_URM09::DFRobot_URM09(i2c_master_bus_handle_t i2c_bus, uint8_t i2c_addr)
  13.     : i2c_bus_(i2c_bus), i2c_dev_(nullptr), i2c_addr_(i2c_addr) {
  14.     // 构造函数体(可为空)
  15. }
  16. // 析构函数
  17. DFRobot_URM09::~DFRobot_URM09() {
  18.     if (i2c_dev_) {
  19.         i2c_master_bus_rm_device(i2c_dev_);
  20.     }
  21. }
  22. // 初始化传感器
  23. bool DFRobot_URM09::begin() {
  24.     // 配置I2C设备
  25.     i2c_device_config_t dev_cfg = {
  26.         .dev_addr_length = I2C_ADDR_BIT_LEN_7,
  27.         .device_address = i2c_addr_,
  28.         .scl_speed_hz = 100000, // 100kHz
  29.     };
  30.    
  31.     esp_err_t ret = i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &i2c_dev_);
  32.     if (ret != ESP_OK) {
  33.         ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(ret));
  34.         return false;
  35.     }
  36.    
  37.     ESP_LOGI(TAG, "URM09 initialized at address 0x%02X", i2c_addr_);
  38.     return true;
  39. }
  40. // 设置测量模式和范围
  41. void DFRobot_URM09::setModeRange(uint8_t range, uint8_t mode) {
  42.     txbuf[0] = (uint8_t)(range | mode);
  43.     i2cWriteRegister(eCFG_INDEX, txbuf, 1);
  44. }
  45. // 发送测量命令
  46. void DFRobot_URM09::measurement() {
  47.     txbuf[0] = CMD_DISTANCE_MEASURE;
  48.     i2cWriteRegister(eCMD_INDEX, txbuf, 1);
  49. }
  50. // 获取温度
  51. float DFRobot_URM09::getTemperature() {
  52.     uint8_t rxbuf[2] = {0};
  53.    
  54.     if (i2cReadRegister(eTEMP_H_INDEX, rxbuf, 2) != ESP_OK) {
  55.         ESP_LOGE(TAG, "Failed to read temperature");
  56.         return -999.0f;
  57.     }
  58.    
  59.     int16_t temp_raw = ((int16_t)rxbuf[0] << 8) | rxbuf[1];
  60.     return temp_raw / 10.0f;
  61. }
  62. // 获取距离
  63. int16_t DFRobot_URM09::getDistance() {
  64.     uint8_t rxbuf[2] = {0};
  65.    
  66.     if (i2cReadRegister(eDIST_H_INDEX, rxbuf, 2) != ESP_OK) {
  67.         ESP_LOGE(TAG, "Failed to read distance");
  68.         return -1;
  69.     }
  70.    
  71.     return ((int16_t)rxbuf[0] << 8) | rxbuf[1];
  72. }
  73. // I2C写寄存器
  74. esp_err_t DFRobot_URM09::i2cWriteRegister(uint8_t reg, uint8_t *data, uint8_t len) {
  75.     uint8_t write_buf[10];
  76.    
  77.     if (len > 9) {
  78.         ESP_LOGE(TAG, "Write data too long");
  79.         return ESP_FAIL;
  80.     }
  81.    
  82.     write_buf[0] = reg;
  83.     memcpy(&write_buf[1], data, len);
  84.    
  85.     return i2c_master_transmit(i2c_dev_, write_buf, len + 1, -1);
  86. }
  87. // I2C读寄存器
  88. esp_err_t DFRobot_URM09::i2cReadRegister(uint8_t reg, uint8_t *data, uint8_t len) {
  89.     // 先发送寄存器地址
  90.     uint8_t reg_addr = reg;
  91.     esp_err_t ret = i2c_master_transmit(i2c_dev_, ®_addr, 1, -1);
  92.     if (ret != ESP_OK) {
  93.         return ret;
  94.     }
  95.    
  96.     // 然后读取数据
  97.     return i2c_master_receive(i2c_dev_, data, len, -1);
  98. }
  99. // 扫描设备
  100. int16_t DFRobot_URM09::scanDevice() {
  101.     // 注意:此函数需要独立I2C操作,可能不适用于已添加到总线的设备
  102.     for (uint8_t address = 1; address < 127; address++) {
  103.         i2c_device_config_t scan_cfg = {
  104.             .dev_addr_length = I2C_ADDR_BIT_LEN_7,
  105.             .device_address = address,
  106.             .scl_speed_hz = 100000,
  107.         };
  108.         
  109.         i2c_master_dev_handle_t scan_dev;
  110.         esp_err_t ret = i2c_master_bus_add_device(i2c_bus_, &scan_cfg, &scan_dev);
  111.         if (ret == ESP_OK) {
  112.             i2c_master_bus_rm_device(scan_dev);
  113.             return address;
  114.         }
  115.     }
  116.     return -1;
  117. }
  118. // 获取当前I2C地址
  119. uint8_t DFRobot_URM09::getI2CAddress() {
  120.     uint8_t rxbuf[1] = {0};
  121.    
  122.     if (i2cReadRegister(eSLAVEADDR_INDEX, rxbuf, 1) != ESP_OK) {
  123.         return 0;
  124.     }
  125.    
  126.     return rxbuf[0];
  127. }
  128. // 修改I2C地址
  129. void DFRobot_URM09::modifyI2CAddress(uint8_t address) {
  130.     txbuf[0] = address;
  131.     i2cWriteRegister(eSLAVEADDR_INDEX, txbuf, 1);
  132.     i2c_addr_ = address; // 更新内部地址
  133.    
  134.     // 需要重新初始化设备
  135.     if (i2c_dev_) {
  136.         i2c_master_bus_rm_device(i2c_dev_);
  137.         i2c_dev_ = nullptr;
  138.     }
  139.    
  140.     // 重新添加设备
  141.     i2c_device_config_t dev_cfg = {
  142.         .dev_addr_length = I2C_ADDR_BIT_LEN_7,
  143.         .device_address = i2c_addr_,
  144.         .scl_speed_hz = 100000,
  145.     };
  146.    
  147.     i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &i2c_dev_);
  148. }
复制代码
将 DFRobot_URM09.cc 源文件添加到编译系统中,确保 URM09 超声波传感器的驱动代码被编译并链接到最终的可执行文件中。
CMakeLists.txt 文件修改,修改位置:在 CMakeLists.txt 的源文件列表部分
  1. list(APPEND SOURCES "boards/df-k10/DFRobot_URM09.cc")  # 新增:URM09超声波传感器驱动
复制代码
board.h 文件修改,修改位置:在 board.h 的类声明中,在公共接口部分"public:"
  1. // ===================== 在 Board 基类中新增传感器数据获取接口 =====================
  2. // 位置:在 board.h 文件的 Board 类定义中,public: 区域
  3. class Board {
  4. public:
  5.     // ... 原有虚函数声明 ...
  6.    
  7.     /**
  8.      * 获取传感器数据的字符串表示
  9.      * 功能:返回当前所有传感器数据的汇总字符串(JSON格式或其他格式)
  10.      * 注意:这是一个虚函数,派生类可以覆盖以提供具体的传感器数据
  11.      * 返回值:传感器数据的字符串表示,默认返回空字符串
  12.      * 使用场景:
  13.      *   1. 用于物联网平台数据上报
  14.      *   2. 用于设备状态监控
  15.      *   3. 用于调试和日志记录
  16.      *
  17.      * 派生类覆盖示例(在 Df_K10Board 中):
  18.      *   std::string GetSensorData() override {
  19.      *       // 获取温度、湿度、距离等传感器数据
  20.      *       char buffer[256];
  21.      *       snprintf(buffer, sizeof(buffer),
  22.      *                "{"temperature":%.1f,"humidity":%.1f,"distance":%d}",
  23.      *                temperature_, humidity_, distance_);
  24.      *       return std::string(buffer);
  25.      *   }
  26.      */
  27.     virtual std::string GetSensorData() { return ""; }  // 默认返回空字符串
  28.    
  29.     // ... 其他虚函数声明 ...
  30. };
复制代码
DFRobot URM09 超声波传感器集成代码
在 df_k10_board.cc 文件中新增内容
  1. // ===================== 新增URM09超声波传感器支持 =====================
  2. // 位置:在文件开头的头文件包含部分
  3. #include "DFRobot_URM09.h"  // 新增:URM09超声波传感器驱动
  4. // ===================== 在 Df_K10Board 类中新增成员变量 =====================
  5. // 位置:在类定义的私有成员部分
  6. private:
  7.     // ... 其他私有成员 ...
  8.    
  9.     // 超声波传感器相关成员
  10.     DFRobot_URM09* urm09_sensor_;    // URM09传感器实例指针
  11.     int16_t last_distance_cm_;       // 最近一次测量的距离(厘米)
  12.     float last_temperature_c_;       // 最近一次测量的温度(摄氏度)
  13.    
  14.     // 新增初始化函数声明
  15.     void InitializeSensors();        // 初始化所有传感器
  16. // ===================== 构造函数修改 =====================
  17. // 位置:在构造函数定义处
  18. Df_K10Board::Df_K10Board()
  19.     : urm09_sensor_(nullptr)       // 在初始化列表中初始化为nullptr
  20.     , last_distance_cm_(0)
  21.     , last_temperature_c_(0.0f)
  22. {
  23.     // 初始化各硬件组件
  24.     InitializeI2c();
  25.     InitializeIoExpander();
  26.     InitializeSpi();
  27.     InitializeIli9341Display();
  28.     InitializeButtons();
  29.     InitializeIot();
  30.     InitializeCamera();
  31.     servo.Initialize();
  32.     servo.InitializeTools();
  33.    
  34.     // 新增传感器初始化
  35.     InitializeSensors();
  36.    
  37.     ESP_LOGI(TAG, "Df_K10Board initialized with URM09 sensor support");
  38. }
  39. // ===================== 析构函数实现 =====================
  40. // 位置:在类实现文件的析构函数部分
  41. Df_K10Board::~Df_K10Board() {
  42.     // 清理URM09传感器资源
  43.     if (urm09_sensor_) {
  44.         delete urm09_sensor_;
  45.         urm09_sensor_ = nullptr;
  46.         ESP_LOGI(TAG, "URM09 sensor resources cleaned up");
  47.     }
  48.    
  49.     // 其他资源清理...
  50. }
  51. // ===================== 新增传感器初始化函数 =====================
  52. /**
  53. * 初始化所有传感器
  54. * 功能:初始化URM09超声波传感器,配置测量模式
  55. * 注意:需要在I2C总线初始化之后调用
  56. */
  57. void Df_K10Board::InitializeSensors() {
  58.     ESP_LOGI(TAG, "Initializing sensors...");
  59.    
  60.     // 确保 I2C 总线已初始化
  61.     if (!i2c_bus_) {
  62.         ESP_LOGE(TAG, "I2C bus not initialized before sensor init");
  63.         return;
  64.     }
  65.    
  66.     // 创建URM09传感器实例
  67.     // 参数:I2C总线指针,设备地址(默认为0x11)
  68.     urm09_sensor_ = new DFRobot_URM09(i2c_bus_, 0x11);
  69.    
  70.     if (urm09_sensor_->begin()) {
  71.         // 配置传感器模式:
  72.         // 测量范围:500cm,测量模式:自动
  73.         urm09_sensor_->setModeRange(MEASURE_RANG_500, MEASURE_MODE_AUTOMATIC);
  74.         ESP_LOGI(TAG, "URM09 sensor initialized successfully");
  75.         
  76.         // 读取初始值
  77.         last_distance_cm_ = urm09_sensor_->getDistance();
  78.         last_temperature_c_ = urm09_sensor_->getTemperature();
  79.         ESP_LOGI(TAG, "Initial distance: %d cm, temperature: %.1f C",
  80.                 last_distance_cm_, last_temperature_c_);
  81.     } else {
  82.         ESP_LOGE(TAG, "Failed to initialize URM09 sensor");
  83.         delete urm09_sensor_;
  84.         urm09_sensor_ = nullptr;
  85.     }
  86. }
  87. // ===================== 实现 GetSensorData 函数 =====================
  88. /**
  89. * 获取传感器数据的字符串表示
  90. * 功能:读取URM09传感器的距离和温度数据,并格式化为字符串
  91. * 返回值:包含距离和温度信息的字符串,格式为"当前距离为X厘米,环境温度为Y摄氏度。"
  92. *        如果传感器未初始化或数据无效,返回相应的错误信息
  93. * 覆盖:此函数覆盖了基类 Board 中的 GetSensorData() 虚函数
  94. */
  95. std::string Df_K10Board::GetSensorData() {
  96.     // 检查传感器是否已初始化
  97.     if (!urm09_sensor_) {
  98.         ESP_LOGW(TAG, "URM09 sensor not initialized");
  99.         return "传感器未初始化";
  100.     }
  101.    
  102.     // 读取最新传感器数据
  103.     last_distance_cm_ = urm09_sensor_->getDistance();
  104.     last_temperature_c_ = urm09_sensor_->getTemperature();
  105.    
  106.     ESP_LOGI(TAG, "Sensor reading - Distance: %d cm, Temperature: %.1f C",
  107.             last_distance_cm_, last_temperature_c_);
  108.    
  109.     // 检查数据有效性
  110.     if (last_distance_cm_ < 0) {
  111.         ESP_LOGW(TAG, "Invalid distance reading: %d cm", last_distance_cm_);
  112.         return "距离数据无效(负值)";
  113.     }
  114.    
  115.     if (last_distance_cm_ > 500) {  // 超出传感器量程
  116.         ESP_LOGW(TAG, "Distance out of range: %d cm", last_distance_cm_);
  117.         return "距离超出量程(超过500厘米)";
  118.     }
  119.    
  120.     // 格式化为中文描述字符串
  121.     char buffer[128];
  122.     snprintf(buffer, sizeof(buffer),
  123.              "当前距离为%d厘米,环境温度为%.1f摄氏度。",
  124.              last_distance_cm_, last_temperature_c_);
  125.    
  126.     return std::string(buffer);
  127. }
复制代码
【360舵机控制】
CMakeLists.txt 文件修改,修改位置:在 CMakeLists.txt 的源文件列表部分
  1. set(SOURCES
  2. "boards/df-k10/servo_controller.cc" # 新增:360舵机驱动
  3.     # ... 其他源文件 ...
  4. )
复制代码
360舵机servo_controller.h库文件内容:
  1. #ifndef __SERVO_CONTROLLER_H__
  2. #define __SERVO_CONTROLLER_H__
  3. #include <driver/ledc.h>
  4. #include <driver/gpio.h>
  5. #include <esp_log.h>
  6. #include <freertos/FreeRTOS.h>
  7. #include <freertos/task.h>
  8. #include <freertos/queue.h>
  9. #include <functional>
  10. #include "config.h"
  11. #include "mcp_server.h"
  12. class ServoController {
  13. public:
  14.     ServoController(gpio_num_t servo_pin);
  15.     ~ServoController();
  16.     // 基本控制方法
  17.     bool Initialize();
  18.     void InitializeTools();  // 初始化MCP工具
  19.    
  20.     // 360度舵机控制方法
  21.     void SetSpeed(int speed);                   // 设置速度 (-100到100)
  22.     void RotateContinuous(int speed);           // 持续旋转
  23.     void RotateForTime(int speed, int duration_ms);  // 旋转指定时间
  24.     void RotateForDegrees(int speed, int degrees);   // 旋转指定角度(估算)
  25.     void Stop();
  26.     void Brake();                               // 刹车(保持当前位置)
  27.    
  28.     // 状态查询
  29.     int GetCurrentSpeed() const { return current_speed_; }
  30.     bool IsMoving() const { return is_moving_; }
  31.     bool IsRotating() const { return is_rotating_; }
  32.    
  33.     // 设置回调函数
  34.     void SetOnMoveCompleteCallback(std::function<void()> callback) {
  35.         on_move_complete_callback_ = callback;
  36.     }
  37. private:
  38.     // 硬件相关
  39.     gpio_num_t servo_pin_;
  40.     ledc_channel_t ledc_channel_;
  41.     ledc_timer_t ledc_timer_;
  42.    
  43.     // 状态变量
  44.     int current_speed_;
  45.     bool is_moving_;
  46.     bool is_rotating_;
  47.     bool stop_requested_;
  48.    
  49.     // 校准参数
  50.     float calibration_factor_;      // 校准因子,调整旋转角度与实际角度关系
  51.     float speed_to_angle_per_sec_;  // 速度到角度/秒的映射
  52.    
  53.     // 任务和队列
  54.     TaskHandle_t servo_task_handle_;
  55.     QueueHandle_t command_queue_;
  56.    
  57.     // 回调函数
  58.     std::function<void()> on_move_complete_callback_;
  59.    
  60.     // 命令类型
  61.     enum CommandType {
  62.         CMD_SET_SPEED,          // 设置速度
  63.         CMD_ROTATE_CONTINUOUS,  // 持续旋转
  64.         CMD_ROTATE_TIME,        // 定时旋转
  65.         CMD_ROTATE_DEGREES,     // 旋转指定角度
  66.         CMD_STOP,
  67.         CMD_BRAKE
  68.     };
  69.    
  70.     // 命令结构
  71.     struct ServoCommand {
  72.         CommandType type;
  73.         int param1;  // 速度
  74.         int param2;  // 持续时间(ms)或角度(degrees)
  75.         int param3;  // 备用
  76.     };
  77.    
  78.     // 私有方法
  79.     void WriteSpeed(int speed);
  80.     uint32_t SpeedToCompare(int speed);
  81.     bool IsValidSpeed(int speed) const;
  82.     int ConstrainSpeed(int speed) const;
  83.    
  84.     // 校准方法
  85.     void CalibrateSpeed(int target_speed, int measured_time_ms, int measured_degrees);
  86.    
  87.     // 任务函数
  88.     static void ServoTask(void* parameter);
  89.     void ProcessCommands();
  90.     void ExecuteSetSpeed(int speed);
  91.     void ExecuteRotateContinuous(int speed);
  92.     void ExecuteRotateForTime(int speed, int duration_ms);
  93.     void ExecuteRotateForDegrees(int speed, int degrees);
  94. };
  95. #endif // __SERVO_CONTROLLER_H__
复制代码
360舵机库文件servo_controller.cc内容:
  1. #include "servo_controller.h"
  2. #include <esp_log.h>
  3. #include <cmath>
  4. #define TAG "ServoController"
  5. ServoController::ServoController(gpio_num_t servo_pin)
  6.     : servo_pin_(servo_pin)
  7.     , current_speed_(0)
  8.     , is_moving_(false)
  9.     , is_rotating_(false)
  10.     , stop_requested_(false)
  11.     , calibration_factor_(1.0f)
  12.     , speed_to_angle_per_sec_(6.0f)  // 默认:100速度对应60度/秒
  13.     , servo_task_handle_(nullptr)
  14.     , command_queue_(nullptr)
  15.     , on_move_complete_callback_(nullptr) {
  16.    
  17.     // 根据 GPIO 引脚分配不同的 LEDC 通道
  18.     // 假设最多支持2个舵机
  19.     static int channel_counter = 0;
  20.     switch (channel_counter++) {
  21.         case 0:
  22.             ledc_channel_ = LEDC_CHANNEL_0;
  23.             ledc_timer_ = LEDC_TIMER_0;
  24.             break;
  25.         case 1:
  26.             ledc_channel_ = LEDC_CHANNEL_1;
  27.             ledc_timer_ = LEDC_TIMER_1;
  28.             break;
  29.         default:
  30.             ledc_channel_ = LEDC_CHANNEL_2;
  31.             ledc_timer_ = LEDC_TIMER_2;
  32.             break;
  33.     }
  34.    
  35.     ESP_LOGI(TAG, "创建360度舵机控制器,引脚: %d", servo_pin_);
  36. }
  37. ServoController::~ServoController() {
  38.     Stop();
  39.     if (servo_task_handle_ != nullptr) {
  40.         vTaskDelete(servo_task_handle_);
  41.     }
  42.     if (command_queue_ != nullptr) {
  43.         vQueueDelete(command_queue_);
  44.     }
  45. }
  46. bool ServoController::Initialize() {
  47.     ESP_LOGI(TAG, "初始化360度舵机控制器,引脚: %d", servo_pin_);
  48.    
  49.     // 配置LEDC定时器 (50Hz,14位分辨率)
  50.     ledc_timer_config_t timer_config = {
  51.         .speed_mode = LEDC_LOW_SPEED_MODE,
  52.         .duty_resolution = LEDC_TIMER_14_BIT,
  53.         .timer_num = ledc_timer_,
  54.         .freq_hz = 50, // 50Hz for servo
  55.         .clk_cfg = LEDC_AUTO_CLK
  56.     };
  57.    
  58.     esp_err_t ret = ledc_timer_config(&timer_config);
  59.     if (ret != ESP_OK) {
  60.         ESP_LOGE(TAG, "LEDC定时器配置失败: %s", esp_err_to_name(ret));
  61.         return false;
  62.     }
  63.    
  64.     // 配置LEDC通道
  65.     ledc_channel_config_t channel_config = {
  66.         .gpio_num = servo_pin_,
  67.         .speed_mode = LEDC_LOW_SPEED_MODE,
  68.         .channel = ledc_channel_,
  69.         .intr_type = LEDC_INTR_DISABLE,
  70.         .timer_sel = ledc_timer_,
  71.         .duty = 0,
  72.         .hpoint = 0
  73.     };
  74.    
  75.     ret = ledc_channel_config(&channel_config);
  76.     if (ret != ESP_OK) {
  77.         ESP_LOGE(TAG, "LEDC通道配置失败: %s", esp_err_to_name(ret));
  78.         return false;
  79.     }
  80.    
  81.     // 创建命令队列
  82.     command_queue_ = xQueueCreate(10, sizeof(ServoCommand));
  83.     if (command_queue_ == nullptr) {
  84.         ESP_LOGE(TAG, "创建命令队列失败");
  85.         return false;
  86.     }
  87.    
  88.     // 创建舵机控制任务
  89.     BaseType_t task_ret = xTaskCreate(
  90.         ServoTask,
  91.         "servo_task",
  92.         4096,
  93.         this,
  94.         5,
  95.         &servo_task_handle_
  96.     );
  97.    
  98.     if (task_ret != pdPASS) {
  99.         ESP_LOGE(TAG, "创建舵机任务失败");
  100.         return false;
  101.     }
  102.    
  103.     // 设置初始速度(停止)
  104.     WriteSpeed(0);
  105.    
  106.     ESP_LOGI(TAG, "360度舵机控制器初始化成功");
  107.     return true;
  108. }
  109. void ServoController::InitializeTools() {
  110.     auto& mcp_server = McpServer::GetInstance();
  111.     ESP_LOGI(TAG, "开始注册360度舵机MCP工具...");
  112.     // 设置速度
  113.     mcp_server.AddTool("self.servo.set_speed",
  114.                        "设置360度舵机旋转速度。speed: 速度值(-100到100,0=停止,负值=逆时针,正值=顺时针)",
  115.                        PropertyList({Property("speed", kPropertyTypeInteger, 0, -100, 100)}),
  116.                        [this](const PropertyList& properties) -> ReturnValue {
  117.                            int speed = properties["speed"].value<int>();
  118.                            SetSpeed(speed);
  119.                            return "舵机速度设置为 " + std::to_string(speed) + " (0-停止,负-逆时针,正-顺时针)";
  120.                        });
  121.     // 持续旋转
  122.     mcp_server.AddTool("self.servo.rotate_continuous",
  123.                        "360度舵机持续旋转。speed: 速度值(-100到100)",
  124.                        PropertyList({Property("speed", kPropertyTypeInteger, 50, -100, 100)}),
  125.                        [this](const PropertyList& properties) -> ReturnValue {
  126.                            int speed = properties["speed"].value<int>();
  127.                            RotateContinuous(speed);
  128.                            return "舵机开始持续旋转,速度: " + std::to_string(speed);
  129.                        });
  130.     // 定时旋转
  131.     mcp_server.AddTool("self.servo.rotate_timed",
  132.                        "360度舵机旋转指定时间。speed: 速度值(-100到100); duration_ms: 持续时间(毫秒)",
  133.                        PropertyList({Property("speed", kPropertyTypeInteger, 50, -100, 100),
  134.                                      Property("duration_ms", kPropertyTypeInteger, 1000, 100, 30000)}),
  135.                        [this](const PropertyList& properties) -> ReturnValue {
  136.                            int speed = properties["speed"].value<int>();
  137.                            int duration = properties["duration_ms"].value<int>();
  138.                            RotateForTime(speed, duration);
  139.                            return "舵机旋转 " + std::to_string(duration) + "ms,速度: " + std::to_string(speed);
  140.                        });
  141.     // 旋转指定角度(估算)
  142.     mcp_server.AddTool("self.servo.rotate_degrees",
  143.                        "360度舵机旋转指定角度(估算)。speed: 速度值(30-100或-30--100); degrees: 旋转角度",
  144.                        PropertyList({Property("speed", kPropertyTypeInteger, 60, -100, 100),
  145.                                      Property("degrees", kPropertyTypeInteger, 90, 1, 3600)}),
  146.                        [this](const PropertyList& properties) -> ReturnValue {
  147.                            int speed = properties["speed"].value<int>();
  148.                            int degrees = properties["degrees"].value<int>();
  149.                            RotateForDegrees(speed, degrees);
  150.                            return "舵机旋转 " + std::to_string(degrees) + "度,速度: " + std::to_string(speed);
  151.                        });
  152.     // 停止舵机
  153.     mcp_server.AddTool("self.servo.stop",
  154.                        "立即停止360度舵机运动",
  155.                        PropertyList(),
  156.                        [this](const PropertyList& properties) -> ReturnValue {
  157.                            Stop();
  158.                            return "舵机已停止";
  159.                        });
  160.     // 刹车(保持当前位置)
  161.     mcp_server.AddTool("self.servo.brake",
  162.                        "360度舵机刹车(保持当前位置)",
  163.                        PropertyList(),
  164.                        [this](const PropertyList& properties) -> ReturnValue {
  165.                            Brake();
  166.                            return "舵机已刹车";
  167.                        });
  168.     // 获取舵机状态
  169.     mcp_server.AddTool("self.servo.get_status",
  170.                        "获取360度舵机当前状态",
  171.                        PropertyList(),
  172.                        [this](const PropertyList& properties) -> ReturnValue {
  173.                            int speed = GetCurrentSpeed();
  174.                            bool moving = IsMoving();
  175.                            bool rotating = IsRotating();
  176.                            std::string status = "{"speed":" + std::to_string(speed) +
  177.                                               ","moving":" + (moving ? "true" : "false") +
  178.                                               ","rotating":" + (rotating ? "true" : "false") + "}";
  179.                            return status;
  180.                        });
  181.     // 校准舵机(高级功能)
  182.     mcp_server.AddTool("self.servo.calibrate",
  183.                        "校准360度舵机速度映射。target_speed: 测试速度; measured_time_ms: 测量时间; measured_degrees: 测量角度",
  184.                        PropertyList({Property("target_speed", kPropertyTypeInteger, 100, -100, 100),
  185.                                      Property("measured_time_ms", kPropertyTypeInteger, 1000, 100, 10000),
  186.                                      Property("measured_degrees", kPropertyTypeInteger, 360, 1, 7200)}),
  187.                        [this](const PropertyList& properties) -> ReturnValue {
  188.                            int target_speed = properties["target_speed"].value<int>();
  189.                            int measured_time = properties["measured_time_ms"].value<int>();
  190.                            int measured_degrees = properties["measured_degrees"].value<int>();
  191.                            CalibrateSpeed(target_speed, measured_time, measured_degrees);
  192.                            return "舵机校准完成,新校准因子: " + std::to_string(calibration_factor_);
  193.                        });
  194.     ESP_LOGI(TAG, "360度舵机MCP工具注册完成");
  195. }
  196. void ServoController::SetSpeed(int speed) {
  197.     if (!IsValidSpeed(speed)) {
  198.         ESP_LOGW(TAG, "无效速度: %d,将限制在有效范围内", speed);
  199.         speed = ConstrainSpeed(speed);
  200.     }
  201.    
  202.     ServoCommand cmd = {CMD_SET_SPEED, speed, 0, 0};
  203.     xQueueSend(command_queue_, &cmd, portMAX_DELAY);
  204. }
  205. void ServoController::RotateContinuous(int speed) {
  206.     speed = ConstrainSpeed(speed);
  207.     ServoCommand cmd = {CMD_ROTATE_CONTINUOUS, speed, 0, 0};
  208.     xQueueSend(command_queue_, &cmd, portMAX_DELAY);
  209. }
  210. void ServoController::RotateForTime(int speed, int duration_ms) {
  211.     if (duration_ms <= 0) {
  212.         ESP_LOGW(TAG, "持续时间必须大于0");
  213.         return;
  214.     }
  215.    
  216.     speed = ConstrainSpeed(speed);
  217.     ServoCommand cmd = {CMD_ROTATE_TIME, speed, duration_ms, 0};
  218.     xQueueSend(command_queue_, &cmd, portMAX_DELAY);
  219. }
  220. void ServoController::RotateForDegrees(int speed, int degrees) {
  221.     if (degrees <= 0) {
  222.         ESP_LOGW(TAG, "旋转角度必须大于0");
  223.         return;
  224.     }
  225.    
  226.     speed = ConstrainSpeed(speed);
  227.     ServoCommand cmd = {CMD_ROTATE_DEGREES, speed, degrees, 0};
  228.     xQueueSend(command_queue_, &cmd, portMAX_DELAY);
  229. }
  230. void ServoController::Stop() {
  231.     stop_requested_ = true;
  232.     ServoCommand cmd = {CMD_STOP, 0, 0, 0};
  233.     xQueueSend(command_queue_, &cmd, 0); // 不等待,立即发送停止命令
  234. }
  235. void ServoController::Brake() {
  236.     ServoCommand cmd = {CMD_BRAKE, 0, 0, 0};
  237.     xQueueSend(command_queue_, &cmd, portMAX_DELAY);
  238. }
  239. void ServoController::WriteSpeed(int speed) {
  240.     speed = ConstrainSpeed(speed);
  241.     uint32_t compare_value = SpeedToCompare(speed);
  242.     ledc_set_duty(LEDC_LOW_SPEED_MODE, ledc_channel_, compare_value);
  243.     ledc_update_duty(LEDC_LOW_SPEED_MODE, ledc_channel_);
  244.     current_speed_ = speed;
  245.    
  246.     ESP_LOGD(TAG, "设置速度: %d, PWM占空比: %lu", speed, compare_value);
  247. }
  248. uint32_t ServoController::SpeedToCompare(int speed) {
  249.     // 360度舵机速度控制:
  250.     // 速度范围:-100 到 100
  251.     // 脉冲宽度范围:1.0ms 到 2.0ms
  252.     // -100: 1.0ms (逆时针全速)
  253.     // 0: 1.5ms (停止)
  254.     // 100: 2.0ms (顺时针全速)
  255.    
  256.     // 将速度映射到脉冲宽度
  257.     float pulse_width_ms;
  258.     if (speed == 0) {
  259.         pulse_width_ms = 1.5f;  // 停止
  260.     } else if (speed > 0) {
  261.         // 顺时针:1.5ms 到 2.0ms
  262.         pulse_width_ms = 1.5f + (speed / 100.0f) * 0.5f;
  263.     } else {
  264.         // 逆时针:1.0ms 到 1.5ms
  265.         pulse_width_ms = 1.5f + (speed / 100.0f) * 0.5f;
  266.     }
  267.    
  268.     // 确保在有效范围内
  269.     if (pulse_width_ms < 1.0f) pulse_width_ms = 1.0f;
  270.     if (pulse_width_ms > 2.0f) pulse_width_ms = 2.0f;
  271.    
  272.     float duty_cycle = pulse_width_ms / 20.0f;  // 20ms周期
  273.     uint32_t compare_value = (uint32_t)(duty_cycle * 16383); // 14-bit resolution (2^14 - 1)
  274.    
  275.     return compare_value;
  276. }
  277. bool ServoController::IsValidSpeed(int speed) const {
  278.     return speed >= -100 && speed <= 100;
  279. }
  280. int ServoController::ConstrainSpeed(int speed) const {
  281.     if (speed < -100) return -100;
  282.     if (speed > 100) return 100;
  283.     return speed;
  284. }
  285. void ServoController::CalibrateSpeed(int target_speed, int measured_time_ms, int measured_degrees) {
  286.     // 计算实际的角度/秒
  287.     float actual_degrees_per_sec = (measured_degrees * 1000.0f) / measured_time_ms;
  288.    
  289.     // 计算校准因子
  290.     calibration_factor_ = actual_degrees_per_sec / (abs(target_speed) * speed_to_angle_per_sec_ / 100.0f);
  291.    
  292.     // 更新速度到角度/秒的映射
  293.     speed_to_angle_per_sec_ = (abs(target_speed) * 6.0f * calibration_factor_) / 100.0f;
  294.    
  295.     ESP_LOGI(TAG, "校准完成: 速度=%d, 时间=%dms, 角度=%d度, 校准因子=%.3f",
  296.              target_speed, measured_time_ms, measured_degrees, calibration_factor_);
  297.     ESP_LOGI(TAG, "新的速度-角度映射: %.2f 度/秒 在 100速度", speed_to_angle_per_sec_ * 100);
  298. }
  299. void ServoController::ServoTask(void* parameter) {
  300.     ServoController* controller = static_cast<ServoController*>(parameter);
  301.     controller->ProcessCommands();
  302. }
  303. void ServoController::ProcessCommands() {
  304.     ServoCommand cmd;
  305.    
  306.     while (true) {
  307.         if (xQueueReceive(command_queue_, &cmd, pdMS_TO_TICKS(100)) == pdTRUE) {
  308.             if (stop_requested_ && cmd.type != CMD_STOP) {
  309.                 continue; // 忽略非停止命令
  310.             }
  311.             
  312.             switch (cmd.type) {
  313.                 case CMD_SET_SPEED:
  314.                     ExecuteSetSpeed(cmd.param1);
  315.                     break;
  316.                     
  317.                 case CMD_ROTATE_CONTINUOUS:
  318.                     ExecuteRotateContinuous(cmd.param1);
  319.                     break;
  320.                     
  321.                 case CMD_ROTATE_TIME:
  322.                     ExecuteRotateForTime(cmd.param1, cmd.param2);
  323.                     break;
  324.                     
  325.                 case CMD_ROTATE_DEGREES:
  326.                     ExecuteRotateForDegrees(cmd.param1, cmd.param2);
  327.                     break;
  328.                     
  329.                 case CMD_STOP:
  330.                     is_moving_ = false;
  331.                     is_rotating_ = false;
  332.                     stop_requested_ = false;
  333.                     WriteSpeed(0);
  334.                     ESP_LOGI(TAG, "舵机停止");
  335.                     break;
  336.                     
  337.                 case CMD_BRAKE:
  338.                     // 刹车:设置速度到0并保持当前位置
  339.                     is_moving_ = false;
  340.                     is_rotating_ = false;
  341.                     WriteSpeed(0);
  342.                     ESP_LOGI(TAG, "舵机刹车");
  343.                     break;
  344.             }
  345.         }
  346.     }
  347. }
  348. void ServoController::ExecuteSetSpeed(int speed) {
  349.     ESP_LOGI(TAG, "设置舵机速度: %d", speed);
  350.     is_moving_ = (speed != 0);
  351.     is_rotating_ = false;
  352.     WriteSpeed(speed);
  353.     if (on_move_complete_callback_) {
  354.         on_move_complete_callback_();
  355.     }
  356. }
  357. void ServoController::ExecuteRotateContinuous(int speed) {
  358.     ESP_LOGI(TAG, "持续旋转,速度: %d", speed);
  359.     is_moving_ = true;
  360.     is_rotating_ = true;
  361.     WriteSpeed(speed);
  362.    
  363.     // 持续旋转直到收到停止命令
  364.     while (!stop_requested_) {
  365.         vTaskDelay(pdMS_TO_TICKS(100));
  366.     }
  367.    
  368.     if (!stop_requested_) {
  369.         WriteSpeed(0);
  370.         is_moving_ = false;
  371.         is_rotating_ = false;
  372.         
  373.         if (on_move_complete_callback_) {
  374.             on_move_complete_callback_();
  375.         }
  376.     }
  377. }
  378. void ServoController::ExecuteRotateForTime(int speed, int duration_ms) {
  379.     ESP_LOGI(TAG, "定时旋转,速度: %d,时间: %dms", speed, duration_ms);
  380.     is_moving_ = true;
  381.     is_rotating_ = true;
  382.     WriteSpeed(speed);
  383.    
  384.     // 计算需要等待的tick数
  385.     TickType_t start_tick = xTaskGetTickCount();
  386.     TickType_t delay_ticks = pdMS_TO_TICKS(duration_ms);
  387.    
  388.     // 等待指定时间,但可以提前被停止
  389.     while ((xTaskGetTickCount() - start_tick) < delay_ticks && !stop_requested_) {
  390.         vTaskDelay(pdMS_TO_TICKS(10));
  391.     }
  392.    
  393.     // 停止舵机
  394.     if (!stop_requested_) {
  395.         WriteSpeed(0);
  396.     }
  397.    
  398.     is_moving_ = false;
  399.     is_rotating_ = false;
  400.    
  401.     if (on_move_complete_callback_ && !stop_requested_) {
  402.         on_move_complete_callback_();
  403.     }
  404. }
  405. void ServoController::ExecuteRotateForDegrees(int speed, int degrees) {
  406.     ESP_LOGI(TAG, "旋转指定角度,速度: %d,角度: %d度", speed, degrees);
  407.    
  408.     // 计算所需时间(基于校准参数)
  409.     float speed_percentage = abs(speed) / 100.0f;
  410.     float degrees_per_sec = speed_to_angle_per_sec_ * 100 * speed_percentage * calibration_factor_;
  411.     int duration_ms = (int)((degrees / degrees_per_sec) * 1000);
  412.    
  413.     ESP_LOGI(TAG, "预计旋转时间: %dms (%.1f 度/秒)", duration_ms, degrees_per_sec);
  414.    
  415.     is_moving_ = true;
  416.     is_rotating_ = true;
  417.     WriteSpeed(speed);
  418.    
  419.     // 等待计算出的时间
  420.     TickType_t start_tick = xTaskGetTickCount();
  421.     TickType_t delay_ticks = pdMS_TO_TICKS(duration_ms);
  422.    
  423.     while ((xTaskGetTickCount() - start_tick) < delay_ticks && !stop_requested_) {
  424.         vTaskDelay(pdMS_TO_TICKS(10));
  425.     }
  426.    
  427.     // 停止舵机
  428.     if (!stop_requested_) {
  429.         WriteSpeed(0);
  430.     }
  431.    
  432.     is_moving_ = false;
  433.     is_rotating_ = false;
  434.    
  435.     if (on_move_complete_callback_ && !stop_requested_) {
  436.         on_move_complete_callback_();
  437.     }
  438. }
复制代码
df_k10_board.cc文件中新增
  1. #include "servo_controller.h"
  2. // 创建360度舵机控制器
  3. ServoController servo(SERVO_PIN_HORIZONTAL);
  4. class Df_K10Board : public WifiBoard {
  5. private:
  6. Df_K10Board(): urm09_sensor_(nullptr)  // 在初始化列表中初始化为nullptr
  7.     , last_distance_cm_(0)
  8.     , last_temperature_c_(0.0f) {
  9.         // 原初始化各硬件组件
  10.         // 新增:舵机初始化
  11.        servo.Initialize();
  12.        servo.InitializeTools();
  13.      }      
  14. }
复制代码




您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail