2018-4-1 17:24:09 [显示全部楼层]
134128浏览
查看: 134128|回复: 6

[教程] 给你的Arduino加上"耳朵"和"嘴巴"之二:OTTO机器人蓝牙功能详解

[复制链接]
       做好了基础的OTTO机器人之后,很多朋友都想和这个萌宠互动,可是对于很多新手来讲,蓝牙模块的烧录以及程序文件的调试都是个麻烦事。其实,即使你不是学硬件和软件专业出身也同样可以玩转Arduino;只是,估计很多朋友跟我一样都是在业余时间玩,没有更多的时间查找资料和翻译英文文档,所以这里就给出大家一个简明的OTTO蓝牙模块教程。
1.    硬件连接如图:
2.    蓝牙模块可以选择HC-05或者HC-06,如果你想修改蓝牙模块默认的名称、密码和波特率就需要烧录。烧录代码如下:
[mw_shl_code=c,true]/*
        http://giltesa.com/?p=11738
        09/08/2012
        (CC) BY-NC-SA: giltesa.com
Modified by Pablo García pabloeweb@gmail.com for Otto DIY robot
ConfigBluetooth HC-06 from Arduino.
Change name, password and baud using serial port, with no user interaction.
1.- Upload this code to Arduino nano (no bluetooth module connected)
2.- unplug USB
3.- Connect HC-06
4.- Power arduino from external power.
5.- Wait for the Arduino´s onboard led to start blinking
6.- Disconnect power. Everything is done.

After upload code you have 10 seconds to connect the module
BT ---- Arduino
TX ----> RX
RX <---- TX

Once the LED off configuration will start and at the end LED will blink
After this you can pair your module
*/
// Options:
        int ArduLED=13;                                //Internal Arduino´s LED
        char ssid[10]                = "Zowi";        // Name for Bluetooth.
        char baudios                 = '8';                   // 1=>1200 baudios, 2=>2400, 3=>4800, 4=>9600 (default), 5=>19200, 6=>38400, 7=>57600, 8=>115200
        char password[10]        = "1234";                // Password for pairing

void setup()
{
        Serial.begin(9600);                //9600bauds is the deafult baudrate for these modules.
                                        //if it´s not working try changing this baudrate to match your HC-06 initial setup
        
        // Waiting time (10 seconds) onboard LED is ON:
                pinMode(ArduLED,OUTPUT);
                digitalWrite(ArduLED,HIGH);
                delay(10000);
                digitalWrite(ArduLED,LOW);
        
Serial.print("AT"); delay(1000); // Now configuration start

Serial.print("AT+NAME"); Serial.print(ssid); delay(1000);   // Change Name of BT

Serial.print("AT+BAUD"); Serial.print(baudios); delay(1000);    // Change Baud

Serial.print("AT+PIN"); Serial.print(password); delay(1000);            // Change Password
}

void loop()
{
        // After programing bluetooth, onboard LED will Blink.
        digitalWrite(ArduLED, !digitalRead(ArduLED));
        delay(500);
}
[/mw_shl_code]
我翻译一下烧录过程:
(1)上传这段代码(HC05_BT_config.ino或HC06_BT_config.ino)至Arduino nano(注意这时不要连接蓝牙模块);
(2)拔下USB线;
(3)连接HC-05或者HC-06模块;
(4)用外接电源给Arduino通电;
(5)等待Arduino板的LED灯直到开始闪烁;
(6)拔下电源,就一切OK了。
另外,默认设置是Arduino板等待10秒开始烧录,接线方式如下:
BT ---- Arduino
TX ----> RX
RX <---- TX
当然还需要连接电源线。Arduino板上LED灯灭的时候配置开始,配置结束后LED灯开始闪烁,这时你就可以使用你的蓝牙模块了。
3.    Arduino nano板上的烧录。Otto官网上的例子很多,我用的是smooth_criminal,注意需要把Oscillator文件夹拷贝到你的Arduino IDE里的libraries文件夹下(同时需要重新启动IDE,不然会提示找不到库文件)。

此外,smooth_criminal里没有整合蓝牙功能,所以需要加入蓝牙模块的控制代码,你可以从otto官网上的代码中获取灵感,也可以直接使用下面的代码烧录:
[mw_shl_code=c,true]//----------------------------------------------------------------
//-- OTTO Dance smooth criminal
//-- Released under a GPL licencse
//-- Originally made for Zowi project remake for Otto
//-- Authors:  Javier Isabel:  javier.isabel@bq.com
//--           Juan Gonzalez (obijuan): juan.gonzalez@bq.com
//-----------------------------------------------------------------
#include <Servo.h>
#include <Oscillator.h>
#include <EEPROM.h>
#include <SoftwareSerial.h>

#define N_SERVOS 4
//-- First step: Configure the pins where the servos are attached
/*
         ---------------
        |     O   O     |
        |---------------|
YR 3==> |               | <== YL 2
         ---------------
            ||     ||
            ||     ||
RR 5==>   -----   ------  <== RL 4
         |-----   ------|
*/
#define EEPROM_TRIM false
// Activate to take callibration data from internal memory
#define TRIM_RR 7
#define TRIM_RL 4
#define TRIM_YR  4
#define TRIM_YL -7
//OTTO.setTrims(-7,-4,-4,7);

#define PIN_RR 5
#define PIN_RL 4
#define PIN_YR 3
#define PIN_YL 2

#define INTERVALTIME 10.0

SoftwareSerial BT(6, 7);  //新建对象,接收脚为6,发送脚为7
char val;  //存储接收的变量
String string;
char command;

Oscillator servo[N_SERVOS];

void goingUp(int tempo);
void drunk (int tempo);
void noGravity(int tempo);
void kickLeft(int tempo);
void kickRight(int tempo);
void run(int steps, int T=500);
void walk(int steps, int T=1000);
void backyard(int steps, int T=3000);
void backyardSlow(int steps, int T=5000);
void turnLeft(int steps, int T=3000);
void turnRight(int steps, int T=3000);
void moonWalkLeft(int steps, int T=1000);
void moonWalkRight(int steps, int T=1000);
void crusaito(int steps, int T=1000);
void swing(int steps, int T=1000);
void upDown(int steps, int T=1000);
void flapping(int steps, int T=1000);

void setup()
{
  Serial.begin(19200);
  BT.begin(115200);  //设置波特率
  
  servo[0].attach(PIN_RR);
  servo[1].attach(PIN_RL);
  servo[2].attach(PIN_YR);
  servo[3].attach(PIN_YL);
  
  //EEPROM.write(0,TRIM_RR);
  //EEPROM.write(1,TRIM_RL);
  //EEPROM.write(2,TRIM_YR);
  //EEPROM.write(3,TRIM_YL);
  
  int trim;
  
  if(EEPROM_TRIM){
    for(int x=0;x<4;x++){
      trim=EEPROM.read(x);
      if(trim>128)trim=trim-256;
      Serial.print("TRIM ");
      Serial.print(x);
      Serial.print(" en ");
      Serial.println(trim);
      servo[x].SetTrim(trim);
    }
  }
  else{
    servo[0].SetTrim(TRIM_RR);
    servo[1].SetTrim(TRIM_RL);
    servo[2].SetTrim(TRIM_YR);
    servo[3].SetTrim(TRIM_YL);
  }
  
  for(int i=0;i<4;i++) servo.SetPosition(90);
}

// TEMPO: 121 BPM
int t=495;
double pause=0;

void loop()
{
if (BT.available() > 0)
    {string = "";}
   
    while(BT.available() > 0)
    {
      command = ((byte)BT.read());
      
      if(command == ':')
      {
        break;
      }
      
      else
      {
        string += command;
      }
      
      delay(1);
    }
   
    if(string == "TO")
    {
       dance();
    }
   
    if(string =="TF")
    {
       for(int i=0;i<4;i++) servo.SetPosition(90);
    }
      
    for(int i=0;i<4;i++) servo.SetPosition(90);

}

void dance(){
  //primera_parte();
  segunda_parte();
  moonWalkLeft(4,t*2);
  moonWalkRight(4,t*2);
  moonWalkLeft(4,t*2);
  moonWalkRight(4,t*2);
  primera_parte();
  crusaito(1,t*8);
  crusaito(1,t*7);

  for (int i=0; i<16; i++){
    flapping(1,t/4);
    delay(3*t/4);
  }

  moonWalkRight(4,t*2);
  moonWalkLeft(4,t*2);
  moonWalkRight(4,t*2);
  moonWalkLeft(4,t*2);

  drunk(t*4);
  drunk(t*4);
  drunk(t*4);
  drunk(t*4);
  kickLeft(t);
  kickRight(t);
  drunk(t*8);
  drunk(t*4);
  drunk(t/2);
  delay(t*4);

  drunk(t/2);

  delay(t*4);
  walk(2,t*2);
  backyard(2,t*2);
  goingUp(t*2);
  goingUp(t*1);
  noGravity(t*2);
  crusaito(1,t*2);
  crusaito(1,t*8);
  crusaito(1,t*2);
  crusaito(1,t*8);
  crusaito(1,t*2);
  crusaito(1,t*3);

  delay(t);
  primera_parte();
    for (int i=0; i<32; i++){
    flapping(1,t/2);
    delay(t/2);
  }
  
  for(int i=0;i<4;i++) servo.SetPosition(90);
}




////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////FUNCIONES DE CONTROL//////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////

void oscillate(int A[N_SERVOS], int O[N_SERVOS], int T, double phase_diff[N_SERVOS]){
  for (int i=0; i<4; i++) {
    servo.SetO(O);
    servo.SetA(A);
    servo.SetT(T);
    servo.SetPh(phase_diff);
  }
  double ref=millis();
   for (double x=ref; x<T+ref; x=millis()){
     for (int i=0; i<4; i++){
        servo.refresh();
     }
  }
}

unsigned long final_time;
unsigned long interval_time;
int oneTime;
int iteration;
float increment[N_SERVOS];
int oldPosition[]={90,90,90,90};

void moveNServos(int time, int  newPosition[]){
  for(int i=0;i<N_SERVOS;i++)  increment = ((newPosition)-oldPosition)/(time/INTERVALTIME);
  
  final_time =  millis() + time;
  
  iteration = 1;
  while(millis() < final_time){ //Javi del futuro cambia esto  
      interval_time = millis()+INTERVALTIME;  
      
      oneTime=0;      
      while(millis()<interval_time){   
          if(oneTime<1){
              for(int i=0;i<N_SERVOS;i++){
                  servo.SetPosition(oldPosition + (iteration * increment));
              }     
              iteration++;
              oneTime++;
          }
      }     
  }   

  for(int i=0;i<N_SERVOS;i++){  
    oldPosition = newPosition;
  }   
}


//////////////////////////////////////////////////////////////////////////////
////////////////////////////////PASOS DE BAILE////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

void goingUp(int tempo){
  
      pause=millis();
      for(int i=0;i<4;i++) servo.SetPosition(90);
      delay(tempo);
      servo[0].SetPosition(80);
      servo[1].SetPosition(100);
      delay(tempo);
      servo[0].SetPosition(70);
      servo[1].SetPosition(110);
      delay(tempo);
      servo[0].SetPosition(60);
      servo[1].SetPosition(120);
      delay(tempo);
      servo[0].SetPosition(50);
      servo[1].SetPosition(130);
      delay(tempo);
      servo[0].SetPosition(40);
      servo[1].SetPosition(140);
      delay(tempo);
      servo[0].SetPosition(30);
      servo[1].SetPosition(150);
      delay(tempo);
      servo[0].SetPosition(20);
      servo[1].SetPosition(160);
      delay(tempo);
      
      while(millis()<pause+8*t);

}

void primera_parte(){
  
  int move1[4] = {60,120,90,90};
  int move2[4] = {90,90,90,90};
  int move3[4] = {40,140,90,90};
  
  for(int x=0; x<3; x++){
    for(int i=0; i<3; i++){
      lateral_fuerte(1,t/2);
      lateral_fuerte(0,t/4);
      lateral_fuerte(1,t/4);
      delay(t);
    }
  
    pause=millis();
    for(int i=0;i<4;i++) servo.SetPosition(90);
    moveNServos(t*0.4,move1);
    moveNServos(t*0.4,move2);
    while(millis()<(pause+t*2));
  }
  
  for(int i=0; i<2; i++){
    lateral_fuerte(1,t/2);
    lateral_fuerte(0,t/4);
    lateral_fuerte(1,t/4);
    delay(t);
  }
   
  pause=millis();
  for(int i=0;i<4;i++) servo.SetPosition(90);
  crusaito(1,t*1.4);
  moveNServos(t*1,move3);
  for(int i=0;i<4;i++) servo.SetPosition(90);
  while(millis()<(pause+t*4));
}

void segunda_parte(){
  
  int move1[4] = {90,90,80,100};
  int move2[4] = {90,90,100,80};
  int move3[4] = {90,90,80,100};
  int move4[4] = {90,90,100,80};
   
  int move5[4] = {40,140,80,100};
  int move6[4] = {40,140,100,80};
  int move7[4] = {90,90,80,100};
  int move8[4] = {90,90,100,80};
      
  int move9[4] = {40,140,80,100};
  int move10[4] = {40,140,100,80};
  int move11[4] = {90,90,80,100};
  int move12[4] = {90,90,100,80};
  
  for(int x=0; x<7; x++){
    for(int i=0; i<3; i++){
      pause=millis();
      moveNServos(t*0.15,move1);
      moveNServos(t*0.15,move2);
      moveNServos(t*0.15,move3);
      moveNServos(t*0.15,move4);
      while(millis()<(pause+t));
    }
    pause=millis();
    moveNServos(t*0.15,move5);
    moveNServos(t*0.15,move6);
    moveNServos(t*0.15,move7);
    moveNServos(t*0.15,move8);
    while(millis()<(pause+t));
  }

  for(int i=0; i<3; i++){
    pause=millis();
    moveNServos(t*0.15,move9);
    moveNServos(t*0.15,move10);
    moveNServos(t*0.15,move11);
    moveNServos(t*0.15,move12);
    while(millis()<(pause+t));
  }
}

void lateral_fuerte(boolean side, int tempo){
  
  for(int i=0;i<4;i++) servo.SetPosition(90);
  if (side) servo[0].SetPosition(40);
  else servo[1].SetPosition(140);
  delay(tempo/2);
  servo[0].SetPosition(90);
  servo[1].SetPosition(90);
  delay(tempo/2);

}

void drunk (int tempo){
  
  pause=millis();
  
  int move1[] = {60,70,90,90};
  int move2[] = {110,120,90,90};
  int move3[] = {60,70,90,90};
  int move4[] = {110,120,90,90};
  
  moveNServos(tempo*0.235,move1);
  moveNServos(tempo*0.235,move2);
  moveNServos(tempo*0.235,move3);
  moveNServos(tempo*0.235,move4);
  while(millis()<(pause+tempo));

}


void noGravity(int tempo){
  
  int move1[4] = {120,140,90,90};
  int move2[4] = {140,140,90,90};
  int move3[4] = {120,140,90,90};
  int move4[4] = {90,90,90,90};
  
  
  for(int i=0;i<4;i++) servo.SetPosition(90);
  for(int i=0;i<N_SERVOS;i++) oldPosition=90;
  moveNServos(tempo*2,move1);
  moveNServos(tempo*2,move2);
  delay(tempo*2);
  moveNServos(tempo*2,move3);
  moveNServos(tempo*2,move4);



}

void kickLeft(int tempo){
  for(int i=0;i<4;i++) servo.SetPosition(90);
  delay(tempo);
  servo[0].SetPosition(50); //pie derecho
  servo[1].SetPosition(70); //pie izquiero
  delay(tempo);
  servo[0].SetPosition(80); //pie derecho
  servo[1].SetPosition(70); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(30); //pie derecho
  servo[1].SetPosition(70); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(80); //pie derecho
  servo[1].SetPosition(70); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(30); //pie derecho
  servo[1].SetPosition(70); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(80); //pie derecho
  servo[1].SetPosition(70); //pie izquiero
  delay(tempo);
}

void kickRight(int tempo){
for(int i=0;i<4;i++) servo.SetPosition(90);
  delay(tempo);
  servo[0].SetPosition(110); //pie derecho
  servo[1].SetPosition(130); //pie izquiero
  delay(tempo);
  servo[0].SetPosition(110); //pie derecho
  servo[1].SetPosition(100); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(110); //pie derecho
  servo[1].SetPosition(150); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(110); //pie derecho
  servo[1].SetPosition(80); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(110); //pie derecho
  servo[1].SetPosition(150); //pie izquiero
  delay(tempo/4);
  servo[0].SetPosition(110); //pie derecho
  servo[1].SetPosition(100); //pie izquiero
  delay(tempo);
}

void walk(int steps, int T){
    int A[4]= {15, 15, 30, 30};
    int O[4] = {0, 0, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(0), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void run(int steps, int T){
    int A[4]= {10, 10, 10, 10};
    int O[4] = {0, 0, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(0), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void backyard(int steps, int T){
    int A[4]= {15, 15, 30, 30};
    int O[4] = {0, 0, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(0), DEG2RAD(-90), DEG2RAD(-90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void backyardSlow(int steps, int T){
    int A[4]= {15, 15, 30, 30};
    int O[4] = {0, 0, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(0), DEG2RAD(-90), DEG2RAD(-90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}


void turnLeft(int steps, int T){
    int A[4]= {20, 20, 10, 30};
    int O[4] = {0, 0, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(0), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void turnRight(int steps, int T){
    int A[4]= {20, 20, 30, 10};
    int O[4] = {0, 0, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(0), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void moonWalkRight(int steps, int T){
    int A[4]= {25, 25, 0, 0};
    int O[4] = {-15 ,15, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(180 + 120), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void moonWalkLeft(int steps, int T){
    int A[4]= {25, 25, 0, 0};
    int O[4] = {-15, 15, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(180 - 120), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void crusaito(int steps, int T){
    int A[4]= {25, 25, 30, 30};
    int O[4] = {- 15, 15, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(180 + 120), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void swing(int steps, int T){
    int A[4]= {25, 25, 0, 0};
    int O[4] = {-15, 15, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(0), DEG2RAD(90), DEG2RAD(90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void upDown(int steps, int T){
    int A[4]= {25, 25, 0, 0};
    int O[4] = {-15, 15, 0, 0};
    double phase_diff[4] = {DEG2RAD(180), DEG2RAD(0), DEG2RAD(270), DEG2RAD(270)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void flapping(int steps, int T){
    int A[4]= {15, 15, 8, 8};
    int O[4] = {-A[0], A[1], 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(180), DEG2RAD(90), DEG2RAD(-90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}

void test(int steps, int T){
    int A[4]= {15, 15, 8, 8};
    int O[4] = {-A[0] + 10, A[1] - 10, 0, 0};
    double phase_diff[4] = {DEG2RAD(0), DEG2RAD(180), DEG2RAD(90), DEG2RAD(-90)};
   
    for(int i=0;i<steps;i++)oscillate(A,O, T, phase_diff);
}[/mw_shl_code]
硬件完成之后就简单了,找个好用的app吧,你可以用官网的app,也可以用下面这个简单超酷的模拟游戏手柄界面的app(Arduino bluetooth controller,你可以在Google Play Store下载,也可以在文末的百度网盘地址下载):
使用前做一个简单的配置,选择一个按键发送指令,在Arduino里我们烧录的指令是“TO”,所以我就选择一个按钮设置成”TO”。
既然是DIY,其他的指令和动作就需要你自己动手完成了,如果想加上语音识别和语音合成,就安装好“叮当6号”app,并设置好对应的配置文件:
[mw_shl_code=bash,true]{
  "send":[
        {"id":1,"listen":"打开","action":"TO","mode":"0","speak":"好的,已打开"},
        {"id":2,"listen":"关闭","action":"TF","mode":"0","speak":"好的,已关闭"},
        {"id":3,"listen":"你好","action":"","mode":"4","speak":"你好,很高兴认识你"},
        {"id":4,"listen":"跳个舞","action":"TO","mode":"9","speak":"test.wav"}
  ]
}
[/mw_shl_code]
其中listen是你说话发出的指令,action就是对应刚才提到的Arduino烧录的指令,mode里0-4对应着发言“男声”、“女声”、“童声”等(9对应播放的mp3文件,需要和app.txt在同一目录下),speak则是你想让otto说的话(需要注意文本需要使用notepad++等编辑器,且保存格式是UTF-8无BOM;同时还要注意所有标点符号都是英文标点符号,即半码格式)。
接下来就是欣赏你的作品了:
资料下载地址:
https://pan.baidu.com/s/19Lzt8RYCNRsSsUndrGsITw



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

 学徒

发表于 2018-9-10 19:28:35

编译你这个smooth_criminal里有整合蓝牙功能的代码 这副模样 是什么回事呀

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

txm派瑞深山锹  高级技师

发表于 2020-1-18 23:48:42

otto bluetooth
回复

使用道具 举报

zsqzsqzs  见习技师

发表于 2020-4-16 09:34:01

学习一下,谢谢
回复

使用道具 举报

罗火火  学徒

发表于 2021-3-25 21:28:21

特 发表于 2018-9-10 19:28
编译你这个smooth_criminal里有整合蓝牙功能的代码 这副模样 是什么回事呀

我也是
回复

使用道具 举报

江志灏  见习技师

发表于 2021-4-27 09:44:53

资料可以共享一下吗?
回复

使用道具 举报

redtxd  见习技师

发表于 2023-4-6 08:02:21

老师:网盘链接不存在了。
回复

使用道具 举报

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

本版积分规则

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

硬件清单

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

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

mail