回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

Arduino IDE K10行空板MCP控制:RGB灯环和继电器 简单

头像 rzyzzxw 2025.09.04 23 0

9.4

 

【目标任务】

行空板K10 主芯片是ESP32 S3 n16r8,所以它可以变身小智AI,同时支持Arduino IDE后,运用xiaozhi-mcp库,它还可以成为受控端,引脚外接模块也可以用小智AI自然语音控制,可玩性大大增强了。

在本帖子中,我将在不用扩展板的情况下,在K10的两个3Pin PH2.0全功能IO接口(支持数字输入输出、模拟输入和PWM输出)上外接RGB灯环和继电器进行基础训练,为实现更多创意做准备。

本帖子中的接线为:

image.png

计划中,我们将用小智AI自然语音来控制K10引脚P0、P1外接灯环的开关、亮度、简单灯效,控制继电器的开启与断开,由它做开关控制其它电器,本帖子中控制了一个小风扇。

086bdb38ffaed51820d537c41634926.jpg

 

 

材料清单

  • 小智AI X1
  • 行空板K10 X1
  • RGB灯环16灯 X1
  • 3V继电器 X1
  • 小风扇和电源 X1

1、准备工作参看Arduino IDE K10行空板MCP控制:屏幕显示与RGB灯- Makelog(造物记)

2、安装RGB支持库如下图:

屏幕截图 2025-09-03 211207.png

3、编写代码,不断优化与调试。

代码如下:

四个控制工具分别控制断电器开关、RGB灯环的亮度、颜色、简单灯效。

屏幕显示连接过程和命令执行状态。

ecce3ff7f59da873be7f599133e20b1.jpg

代码
#include <WiFi.h>
#include <WebSocketMCP.h>
#include "unihiker_k10.h"
#include <Adafruit_NeoPixel.h>

// WiFi配置
const char* ssid = "your ssid";
const char* password = "your password";

// MCP服务器配置
const char* mcpEndpoint = "ws://api.xiaozhi.me/mcp/?token=your token";

WebSocketMCP mcpClient;
UNIHIKER_K10 k10;
uint8_t screen_dir = 2;

// 引脚定义
#define RGB_LED_PIN P0
#define RELAY_PIN P1

// RGB灯环设置
#define NUM_LEDS 16
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, RGB_LED_PIN, NEO_GRB + NEO_KHZ800);

// 设备状态
bool relayState = false;
uint8_t rgbBrightness = 100;
uint32_t rgbColor = 0xFFFFFF;

// 非阻塞动画控制变量
unsigned long previousMillis = 0;
uint8_t animationMode = 0; // 0: off, 1: rainbow, 2: chase, 3: theater
uint16_t j = 0; // 彩虹效果计数器

// 屏幕状态缓存
bool lastRelayState = false;
uint8_t lastRgbBrightness = 0;
bool lastMcpConnected = false;
IPAddress lastIP;

void setup() {
  Serial.begin(115200);
  
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
  
  strip.begin();
  strip.setBrightness(50);
  strip.show();
  
  k10.begin();
  k10.initScreen(screen_dir);
  k10.creatCanvas();
  k10.setScreenBackground(0x000000);
  
  // 初始屏幕显示
  k10.canvas->canvasText("K10智能控制系统", 10, 10, 0xFFFFFF, k10.canvas->eCNAndENFont24, 200, true);
  k10.canvas->canvasText("等待WiFi连接...", 10, 50, 0xFFFFFF, k10.canvas->eCNAndENFont16, 200, true);
  k10.canvas->updateCanvas();
  
  // 改进的WiFi连接带超时
  WiFi.begin(ssid, password);
  unsigned long startAttemptTime = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 30000) {
    delay(500);
    Serial.print(".");
  }
  
  if (WiFi.status() != WL_CONNECTED) {
    k10.canvas->canvasClear();
    k10.canvas->canvasText("WiFi连接失败", 10, 10, 0xFF0000, k10.canvas->eCNAndENFont16, 200, true);
    k10.canvas->updateCanvas();
    while(1) delay(1000); // 停止运行
  }
  
  // 初始化状态缓存
  lastIP = WiFi.localIP();
  lastMcpConnected = false;
  
  // 只更新连接状态部分
  k10.canvas->canvasText("WiFi已连接", 10, 50, 0xFFFFFF, k10.canvas->eCNAndENFont16, 200, true);
  k10.canvas->canvasText("IP: " + lastIP.toString(), 10, 80, 0xFFFFFF, k10.canvas->eCNAndENFont16, 200, true);
  k10.canvas->updateCanvas();
  delay(2000);

  mcpClient.begin(mcpEndpoint, [](bool connected) {
    if (connected) {
      Serial.println("已连接到MCP服务器");
      lastMcpConnected = true;
      
      // 只更新连接状态部分
      k10.canvas->canvasText("MCP连接成功", 10, 110, 0x00FF00, k10.canvas->eCNAndENFont16, 200, true);
      k10.canvas->updateCanvas();
      
      registerTools();
    } else {
      Serial.println("与MCP服务器断开连接");
      lastMcpConnected = false;
      
      // 只更新连接状态部分
      k10.canvas->canvasText("MCP连接断开", 10, 110, 0xFF0000, k10.canvas->eCNAndENFont16, 200, true);
      k10.canvas->updateCanvas();
    }
  });
}

void registerTools() {
  mcpClient.registerTool(
    "relay_control",
    "控制继电器开关",
    "{\"type\":\"object\",\"properties\":{\"state\":{\"type\":\"string\",\"enum\":[\"on\",\"off\"]}},\"required\":[\"state\"]}",
    [](const String& args) {
      DynamicJsonDocument doc(128);
      DeserializationError error = deserializeJson(doc, args);
      if (error) {
        return WebSocketMCP::ToolResponse("{\"success\":false,\"error\":\"JSON解析失败\"}");
      }
      String state = doc["state"].as<String>();
      
      if (state == "on") {
        digitalWrite(RELAY_PIN, HIGH);
        relayState = true;
      } else if (state == "off") {
        digitalWrite(RELAY_PIN, LOW);
        relayState = false;
      }
      
      // 只更新继电器状态部分
      if (relayState != lastRelayState) {
        k10.canvas->canvasText("继电器状态: " + String(relayState ? "开启" : "关闭"), 10, 140, 
                              relayState ? 0x00FF00 : 0xFF0000, k10.canvas->eCNAndENFont16, 200, true);
        k10.canvas->updateCanvas();
        lastRelayState = relayState;
      }
      
      return WebSocketMCP::ToolResponse("{\"success\":true,\"state\":\"" + state + "\"}");
    }
  );
  
  mcpClient.registerTool(
    "rgb_control",
    "控制RGB灯环",
    "{\"type\":\"object\",\"properties\":{\"red\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255},\"green\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255},\"blue\":{\"type\":\"number\",\"minimum\":0,\"maximum\":255}},\"required\":[\"red\",\"green\",\"blue\"]}",
    [](const String& args) {
      DynamicJsonDocument doc(128);
      DeserializationError error = deserializeJson(doc, args);
      if (error) {
        return WebSocketMCP::ToolResponse("{\"success\":false,\"error\":\"JSON解析失败\"}");
      }
      
      uint8_t r = doc["red"];
      uint8_t g = doc["green"];
      uint8_t b = doc["blue"];
      
      rgbColor = (r << 16) | (g << 8) | b;
      animationMode = 0; // 停止动画
      
      for(int i = 0; i < strip.numPixels(); i++) {
        strip.setPixelColor(i, strip.Color(r, g, b));
      }
      strip.show();
      
      return WebSocketMCP::ToolResponse("{\"success\":true,\"message\":\"RGB灯环颜色已设置\"}");
    }
  );
  
  mcpClient.registerTool(
    "rgb_mode",
    "控制RGB灯环模式",
    "{\"type\":\"object\",\"properties\":{\"mode\":{\"type\":\"string\",\"enum\":[\"rainbow\",\"chase\",\"theater\",\"off\"]}},\"required\":[\"mode\"]}",
    [](const String& args) {
      DynamicJsonDocument doc(128);
      DeserializationError error = deserializeJson(doc, args);
      if (error) {
        return WebSocketMCP::ToolResponse("{\"success\":false,\"error\":\"JSON解析失败\"}");
      }
      String mode = doc["mode"].as<String>();
      
      if (mode == "rainbow") {
        animationMode = 1;
        j = 0;
      } else if (mode == "chase") {
        animationMode = 2;
      } else if (mode == "theater") {
        animationMode = 3;
      } else if (mode == "off") {
        animationMode = 0;
        for(int i = 0; i < strip.numPixels(); i++) {
          strip.setPixelColor(i, 0);
        }
        strip.show();
      }
      
      return WebSocketMCP::ToolResponse("{\"success\":true,\"mode\":\"" + mode + "\"}");
    }
  );
  
  mcpClient.registerTool(
    "rgb_brightness",
    "控制RGB灯环亮度",
    "{\"type\":\"object\",\"properties\":{\"brightness\":{\"type\":\"number\",\"minimum\":0,\"maximum\":100}},\"required\":[\"brightness\"]}",
    [](const String& args) {
      DynamicJsonDocument doc(128);
      DeserializationError error = deserializeJson(doc, args);
      if (error) {
        return WebSocketMCP::ToolResponse("{\"success\":false,\"error\":\"JSON解析失败\"}");
      }
      rgbBrightness = doc["brightness"];
      
      strip.setBrightness(map(rgbBrightness, 0, 100, 0, 255));
      strip.show();
      
      // 只更新亮度部分
      if (rgbBrightness != lastRgbBrightness) {
        k10.canvas->canvasText("RGB亮度: " + String(rgbBrightness) + "%", 10, 170, 
                              0xFFFFFF, k10.canvas->eCNAndENFont16, 200, true);
        k10.canvas->updateCanvas();
        lastRgbBrightness = rgbBrightness;
      }
      
      return WebSocketMCP::ToolResponse("{\"success\":true,\"brightness\":" + String(rgbBrightness) + "}");
    }
  );
  
  Serial.println("所有工具已注册");
  
  // 初始显示设备状态
  k10.canvas->canvasText("继电器状态: " + String(relayState ? "开启" : "关闭"), 10, 140, 
                        relayState ? 0x00FF00 : 0xFF0000, k10.canvas->eCNAndENFont16, 200, true);
  k10.canvas->canvasText("RGB亮度: " + String(rgbBrightness) + "%", 10, 170, 
                        0xFFFFFF, k10.canvas->eCNAndENFont16, 200, true);
  k10.canvas->updateCanvas();
}

// 非阻塞彩虹效果
void updateRainbow() {
  for(int i=0; i< strip.numPixels(); i++) {
    strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
  }
  strip.show();
  j = (j + 1) % (256 * 5);
}

// 非阻塞追逐效果
void updateChase() {
  static int pos = 0;
  strip.setPixelColor(pos, strip.Color(255, 0, 0));
  if (pos > 0) strip.setPixelColor(pos-1, 0);
  strip.show();
  pos = (pos + 1) % strip.numPixels();
}

// 非阻塞剧院效果
void updateTheaterChase() {
  static int q = 0;
  for (int i=0; i < strip.numPixels(); i=i+3) {
    strip.setPixelColor(i+q, strip.Color(0, 0, 255));
  }
  strip.show();
  delay(50);
  for (int i=0; i < strip.numPixels(); i=i+3) {
    strip.setPixelColor(i+q, 0);
  }
  q = (q + 1) % 3;
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void loop() {
  mcpClient.loop();
  
  // 非阻塞动画更新
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 50) {
    previousMillis = currentMillis;
    
    switch(animationMode) {
      case 1: updateRainbow(); break;
      case 2: updateChase(); break;
      case 3: updateTheaterChase(); break;
    }
  }
  
  // 定期检查连接状态,但只在状态变化时更新屏幕
  static unsigned long lastStatusCheck = 0;
  if (currentMillis - lastStatusCheck >= 1000) {
    lastStatusCheck = currentMillis;
    
    bool currentConnected = mcpClient.isConnected();
    IPAddress currentIP = WiFi.localIP();
    
    // 只在状态变化时更新屏幕
    if (currentConnected != lastMcpConnected) {
      k10.canvas->canvasText("MCP连接状态: " + String(currentConnected ? "已连接" : "断开"), 10, 200, 
                            currentConnected ? 0x00FF00 : 0xFF0000, k10.canvas->eCNAndENFont16, 200, true);
      k10.canvas->updateCanvas();
      lastMcpConnected = currentConnected;
    }
    
    if (currentIP != lastIP) {
      k10.canvas->canvasText("IP: " + currentIP.toString(), 10, 230, 
                            0xFFFFFF, k10.canvas->eCNAndENFont16, 200, true);
      k10.canvas->updateCanvas();
      lastIP = currentIP;
    }
  }
}

上传测试通过。

小智后台:

image.png

 

 

评论

user-avatar