基于dueros的智能语言家居音响-成都武侯国际创客节-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4757|回复: 7

基于dueros的智能语言家居音响---成都武侯国际创客节

[复制链接]
发表于 2018-5-30 13:58 | 显示全部楼层 |阅读模式
本帖最后由 芥末秋刀鱼 于 2018-5-30 14:03 编辑

一、项目介绍
      如今智能家居的出现在不断的提高人们的生活体验,让生活变得更便捷,现如今又加入了物联网的应用,可以将我们自己的家“接入”互联网,而智能家居很关键的一部分就是人于家具交互的方式,现在大多都是使用手机上的app来实现家具的控制,但如果使用语言来控制的话相比会更加的快捷,方便。想想,在沙发上说一句“打开电视”电视就会自动打开的感觉,家里的一切都有了大脑,能够理解你想让它做什么,这种交互方式一定是未来的趋向。
    IMG_0482.JPG
二、基本介绍

     dueros智能语言家居音响控制系统可以实现通过语音来控制家中像如电灯,电视,空调等家具。同时还可以与音响进行互动,让它给你的孩子讲故事,做游戏,播放音乐等,使用的硬件有树莓派,esp32,ws2812,dueros开发板。
IMG_0479.JPG
三、原理简介
Snipaste_2018-05-30_13-51-13.png
  dueros智能语言家居音响控制系统主要分为两部分:一是搭载在树莓派上百度的dueros,可以识别中文,进行中文对话,查询天气、放歌等,通过双麦克风来采集用户发出的语音命令然后传输给树莓派,树莓派对其处理后传输给百度云平台进一步处理,解析命令,然后将命令返回给树莓派,树莓派再将获得的命令发送给OneNote云平台的一个数据留中; 二是ESP32连接WiFi后接入ONENET平台的同一个数据流中获取树莓派上识别出的命令然后控制灯、舵机/电视等。
四、程序与硬件连接

树莓派部分:

IMG_0480.JPG
程序:
[mw_shl_code=c,true]#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>

#include <unistd.h>
#include <time.h>
#include "MQTTClient.h"

#define ADDRESS "tcp://183.230.40.39:6002"//这里替换成OneNET的,或者百度物接入IoT的(这个数固定的)
#define CLIENTID "你自己的" //clientID在OneNET上是 设备ID
#define TOPIC "你自己的"//主题在OneNET上是 数据流模板里面的数据流名称
//#define PAYLOAD "Hello World!"//这里是我们要测试发送的内容
#define QOS 1
#define TIMEOUT 10000L
volatile MQTTClient_deliveryToken deliveredtoken;

//这两行也弄成全局的吧
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
//这里添加了两行
MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;

int sign=0;//暂时定一个标志,代表有指令要发送
char cmd[1000];//1K字符数组来保存要stringcmp后的指令。

char *word[180]={
//两个词一组排好,把左边的替换成右边的,""代表删除左边的词
"给我","",
"把我","",
"的","",
"就","",
"是","",
"麻烦","",
"请您","",
"请你","",
"求你","",
"求您","",
"我们","",
"他们","",
"她们","",
"客人","",
"朋友","",
"闺蜜","",
"主人","",
"你","",
"您","",
"我","",
"把","",
"一个","",
"个","",
"快点","",
"赶快","",
"立即","",
"马上","",
"迅速","",
"火速","",
"打开","开",
"开下","开",
"关掉","关",
"关了","关",
"关上","关",
"关闭","关",
"一下","",
"下","",
"想看","开",
"要看一下","开",
"要看","开",
"马上","",
"现在","",
"一会","",
"儿","",
"会","",
"一点","点",
"点亮","开",
"点","",
"电视放开","电视开",
"放电视","开电视",
"烧热水","开热水器",
"烧洗澡水","开热水器",
"澡澡","澡",
"想洗澡","开热水器",
"要洗澡","开热水器",
"什么","",
"啥都","",
"都","",
"啥","",
"也","",
"啦","",
"看不见","开灯",
"太黑了","开灯",
"太暗了","开灯",
"睡觉","关灯",
"太亮了","关灯",
"了","",
"等一下","",
"等","",
"去","",
"嘛","",
"呗","",
"都","",
"好","",
"吧","",
"吗","",
"啊","",
"可不","",
"可以","",
"可","",
"能不","",
"能","",
"要放电视","开电视",
"不要放电视","关电视",
"不开电视","关电视",
"家里","",
"家庭","",
"用电器","电器",
"电器关","关",
"全部关","关",
"电器","",
"全关","关所有",
"开单","开灯"
};

/*字符串对比*/
int stringcmp(char *a,char *b)
{
    int i=0;
        for(i=0;a!='\0' && b!='\0';i++)
            if(a!=b){
                break;
            }
        if(a==b)
            return 1;
        else
            return 0;
}
/*将str1字符串中第一次出现的str2字符串替换成str3*/  
void replaceFirst(char *str1,char *str2,char *str3)  
{  
    char str4[strlen(str1)+1];  
    char *p;  
    strcpy(str4,str1);  
    if((p=strstr(str1,str2))!=NULL)/*p指向str2在str1中第一次出现的位置*/  
    {  
        while(str1!=p&&str1!=NULL)/*将str1指针移动到p的位置*/  
        {  
            str1++;  
        }  
        str1[0]='\0';/*将str1指针指向的值变成/0,以此来截断str1,舍弃str2及以后的内容,只保留str2以前的内容*/  
        strcat(str1,str3);/*在str1后拼接上str3,组成新str1*/  
        strcat(str1,strstr(str4,str2)+strlen(str2));/*strstr(str4,str2)是指向str2及以后的内容(包括str2),strstr(str4,str2)+strlen(str2)就是将指针向前移动strlen(str2)位,跳过str2*/  
    }  
}  
/*将str1出现的所有的str2都替换为str3*/  
void replace(char *str1,char *str2,char *str3)  
{  
    while(strstr(str1,str2)!=NULL)  
    {  
        replaceFirst(str1,str2,str3);  
    }  
}

/*返回str1中最后一次出现str2的位置(下标),不存在返回-1*/
int lastIndexOf(char *str1,char *str2) {
        char *p=str1;
        int i=0,len=strlen(str2);
        p=strstr(str1,str2);
        if(p==NULL) return -1;
        while(p!=NULL) {
                for(;str1!=p;str1++)i++;
                p=p+len;
                p=strstr(p,str2);
        }
        return i;
}

//发送消息时候调用
int publish()
{
pubmsg.payload = cmd;
pubmsg.payloadlen = strlen(cmd);
pubmsg.qos = QOS;
pubmsg.retained = 0;
MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token);
}

/*对日志抓取和解析指令*/
int Pretreatment()   
{   
FILE *fstream=NULL;
char buff[1024];//定义1K数组来保存获得的日志内容
char result[1000];//定义1000字节组数来保存语音识别结果
int strpoint;
int j=0;
int i=0;
memset(buff,0,sizeof(buff)); //清空数组
memset(result,0,sizeof(result));
//打开一个通向tail的管道
if(NULL==(fstream=popen("tail -f /duer/duer_linux.log","r")))
{
     printf("管道打不开!");
     return 1;
}
while (NULL!=fgets(buff, sizeof(buff), fstream))//这里fgets()是阻塞的
{
    strpoint=lastIndexOf(&buff[0],"-命中唤醒词");
        if(strpoint!=-1)               
        {
                sprintf(cmd,"%s", "{\"cmd\":\"WakeUP\"}");
                sign=1;
        }
        else
        {
                strpoint=lastIndexOf(&buff[0],"-识别结果:");
                if(strpoint!=-1)               
                {
                        j=0;
                        //下面这段代码用于实现字符串截取功能,提取语音识别结果
                        for(j=strpoint+14;buff[j]!='\0';j++)result[j-strpoint-14]=buff[j];
                        result[j-strpoint-15]='\0';
                        printf("识别结果为:%s\n",result);
                        for(i=0;i<180;i+=2)replace(result,word,word[i+1]);
                            printf("替换后的结果:%s\n",result);
                        if(stringcmp(result,"开电视") || stringcmp(result,"电视开")) {
                                sprintf(cmd,"%s", "{\"cmd\":\"OpenTV\"}");
                                sign=1;
                        }
                        else if(stringcmp(result,"关电视") || stringcmp(result,"电视关")) {
                                sprintf(cmd,"%s", "{\"cmd\":\"CloseTV\"}");
                                sign=1;
                        }
                        else if(stringcmp(result,"关所有")|| stringcmp(result,"所有关")|| stringcmp(result,"关")) {
                                sprintf(cmd,"%s", "{\"cmd\":\"CloseALL\"}");
                                sign=1;
                        }
                        else if(stringcmp(result,"开热水器")|| stringcmp(result,"热水器开")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"OpenRSQ\"}");
                            sign=1;  
                        }
                        else if(stringcmp(result,"关热水器")|| stringcmp(result,"热水器关")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseRSQ\"}");
                                sign=1;
                        }
                        else if(stringcmp(result,"开卧室灯")|| stringcmp(result,"卧室灯开")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"OpenWSD\"}");
                                sign=1;
                        }
                        else if(stringcmp(result,"关卧室灯")|| stringcmp(result,"卧室灯关")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseWSD\"}");
                         sign=1;
                        }
                        else if(stringcmp(result,"开客厅灯")|| stringcmp(result,"客厅灯开")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"OpenKTD\"}");
                         sign=1;
                        }
                        else if(stringcmp(result,"关客厅灯")|| stringcmp(result,"客厅灯关")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseKTD\"}");
                        sign=1;
                        }
                        else if(stringcmp(result,"开餐厅灯")|| stringcmp(result,"餐厅灯开")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"OpenCTD\"}");
                        sign=1;
                        }
                        else if(stringcmp(result,"关餐厅灯")|| stringcmp(result,"餐厅灯关")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseCTD\"}");
                        sign=1;
                        }
                        else if(stringcmp(result,"门开")|| stringcmp(result,"开门")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"OpenDOO\"}");
                        sign=1;
                        }
                        else if(stringcmp(result,"门关")|| stringcmp(result,"关门")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseDOO\"}");
                        sign=1;
                        }
                        else if(stringcmp(result,"关书房灯")|| stringcmp(result,"书房灯关")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseSFD\"}");
                        sign=1;
                        }
                        else if(stringcmp(result,"开书房灯")|| stringcmp(result,"书房灯开")) {
                        sprintf(cmd,"%s", "{\"cmd\":\"OpenSFD\"}");
                        sign=1;
                        }

                        else if(stringcmp(result,"关台灯")|| stringcmp(result,"台灯关")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseTD\"}");
                         sign=1;
                        }
                        else if(stringcmp(result,"关灯")|| stringcmp(result,"灯关")|| stringcmp(result,"所有灯关")|| stringcmp(result,"关所有灯")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CloseALL\"}");
                         sign=1;
                        }
                        else if(stringcmp(result,"开灯")|| stringcmp(result,"灯开")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"OpenALL\"}");
                         sign=1;
                        }
                        else if(stringcmp(result,"扫地")|| stringcmp(result,"地扫")) {
                            sprintf(cmd,"%s", "{\"cmd\":\"CleanRM\"}");
                         sign=1;
                        }
                }
        }
        if(sign==1)
        {
                publish();
                sign=0;
        }
}
pclose(fstream);
return 0;
}


int ping()
{
        FILE *fstream=NULL;
        char buff[512];//定义1K数组来保存获得的日志内容
        memset(buff,0,sizeof(buff)); //清空数组
        int k=0;
        int strpoint;
        //打开一个通向tail的管道
while(k==0)
{
        if(NULL==(fstream=popen("ping www.baidu.com","r")))
         {
     printf("管道打不开!");
     return 0;
         }
        while (NULL!=fgets(buff, sizeof(buff), fstream))//这里fgets()是阻塞的
        {
                printf(buff);
                strpoint=lastIndexOf(&buff[0]," bytes from ");
                if(strpoint!=-1)               
                {
                        k=1;
                        break;
                }
        }
        pclose(fstream);
        if(k==0)
                sleep(6);
}
return 1;
}



//貌似是发送消息后的回调
void delivered(void *context, MQTTClient_deliveryToken dt)
{
//printf("Message with token value %d delivery confirmed\n", dt);
deliveredtoken = dt;
}

//收到消息后的回调
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
int i;
char* payloadptr;
printf("收到消息!\n");
printf(" 主题为: %s\n", topicName);
printf(" 消息内容: ");
payloadptr = message->payload;
for(i=0; i<message->payloadlen; i++)
{
putchar(*payloadptr++);
}
putchar('\n');
MQTTClient_freeMessage(&message);
MQTTClient_free(topicName);
return 1;
}
//断线后的回调
void connlost(void *context, char *cause)
{
printf("\nConnection lost\n");
printf(" cause: %s\n", cause);
exit(0);//暂时先强退吧,后面有空把断线重连之类的代码写进去
}
int main(int argc, char* argv[])
{
int rc;
int ch;
memset(cmd,0,sizeof(cmd));//清空一下cmd数组
ping();//一直ping 百度官网,ping通了再执行后面的数据。
MQTTClient_create(&client, ADDRESS, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL);
conn_opts.keepAliveInterval = 130;//OneNET的keepalive需要120秒以上
conn_opts.cleansession = 1;
conn_opts.username = "你自己的";//这里添加用户名参数,在OneNET上对应的是 产品ID
conn_opts.password = "你自己的";//这里添加密码参数,在OneNET上对应之前提到过的 鉴权信息
MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered);
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to connect, return code %d\n", rc);
exit(EXIT_FAILURE);
}
printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n"
"Press Q<Enter> to quit\n\n", TOPIC, CLIENTID, QOS);
MQTTClient_subscribe(client, TOPIC, QOS);//这里按平常的说法就是 加入QQ群了

while(1)
{
        Pretreatment();//这里去读取识别结果和解析文本
}
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
return rc;
}[/mw_shl_code]
ESP32
部分:


dueros.png
程序:
[mw_shl_code=c,true]#include <ArduinoJson.h>  //JSON库
//mqtt库
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
//舵机
#include <ESP32_Servo.h>
Servo servo1;
//wifi部分
#include <WiFi.h>
#include <WiFiMulti.h>
WiFiMulti wifiMulti;
//唤醒灯WS2812
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif

#define PIN            D6

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS      16
// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
// example for more information on possible values.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

//onenet设置
#define AIO_SERVER      "183.230.40.39"//OneNET服务器ip地址(固定的)
#define AIO_SERVERPORT  6002 //OneNET服务器端口(没引号!)
#define AIO_USERNAME    "你自己的" //OneNET的产品ID
#define TOPIC           "你自己的"//OneNET上的数据流模板
//电视机
#define AIO_USERID      "你自己的"//OneNET的设备ID(不能和树莓派的一样,另一个的ID)
#define AIO_KEY         "你自己的"//OneNET的鉴权信息


//设置LED 灯
#define LED_PIN D9  //WIFI连接灯_板载
#define LIGHT_LIVING D2
#define LIGHT_BEDROOM D3
#define LIGHT_RES D4
#define LIGHT_STUDY D7

//舵机
int servo1Pin = D5;
int minUs = 500;
int maxUs = 2400;
int pos = 0;


WiFiClient client;

// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERID, AIO_USERNAME, AIO_KEY);

/****************************** Feeds ***************************************/

Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt,TOPIC);
// Setup a feed called 'onoff' for subscribing to changes to the button
Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt,TOPIC, MQTT_QOS_1);


//收到消息后的回调,控制端指令就在这里接收处理了
void onoffcallback(char *data, uint16_t len) {
   Serial.println(data);
   StaticJsonBuffer<200> jsonBuffer;
   JsonObject& root = jsonBuffer.parseObject(data);
   if (!root.success()){
      Serial.println("parseObject() failed");
      return;
   }
   String  cmd  = root["cmd"];
   Serial.println(cmd.equals(""));
   //这里可以各种if判断cmd变量收到的是什么指令
    if(cmd.equals("WakeUP"))//如果收到唤醒信号就”睁开眼睛“
    {
    for(int i=0;i<NUMPIXELS;i++){

    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(255,127,0)); // Moderately bright green color.

    pixels.show(); // This sends the updated pixel color to the hardware.

    delay(40); // Delay for a period of time (in milliseconds).

  }
    for(int j=0;j<NUMPIXELS;j++){

    pixels.setPixelColor(j, pixels.Color(0,0,0));

    pixels.show();

    delay(40);

  }

    }
    //总灯
    if(cmd.equals("OpenALL"))
    {
         digitalWrite(LIGHT_LIVING,HIGH);
         digitalWrite(LIGHT_BEDROOM,HIGH);
         digitalWrite(LIGHT_RES,HIGH);
         digitalWrite(LIGHT_STUDY,HIGH);
    }
    if(cmd.equals("CloseALL"))
    {
         digitalWrite(LIGHT_LIVING,LOW);
         digitalWrite(LIGHT_BEDROOM,LOW);
         digitalWrite(LIGHT_RES,LOW);
         digitalWrite(LIGHT_STUDY,LOW);
    }
    //客厅
    if(cmd.equals("OpenKTD"))
    {
      digitalWrite(LIGHT_LIVING,HIGH);
    }
    if(cmd.equals("CloseKTD"))
    {
      digitalWrite(LIGHT_LIVING,LOW);
    }
    //卧室
    if(cmd.equals("OpenWSD"))
    {
      digitalWrite(LIGHT_BEDROOM,HIGH);
    }
    if(cmd.equals("CloseWSD"))
    {
      digitalWrite(LIGHT_BEDROOM,LOW);
    }
    //餐厅
    if(cmd.equals("OpenCTD"))
    {
      digitalWrite(LIGHT_RES,HIGH);
    }
    if(cmd.equals("CloseCTD"))
    {
      digitalWrite(LIGHT_RES,LOW);
    }
    //书房
    if(cmd.equals("OpenSFD"))
    {
      digitalWrite(LIGHT_STUDY,HIGH);
    }
    if(cmd.equals("CloseSFD"))
    {
      digitalWrite(LIGHT_STUDY,LOW);
    }
    //门
    if(cmd.equals("OpenDOO"))
    {
      servo1.write(180);
    }
    if(cmd.equals("CloseDOO"))
    {
      servo1.write(0);
    }
}


void setup() {
  // put your setup code here, to run once:
   Serial.begin(115200);
   delay(10);
   //板载灯
   pinMode(LED_PIN, OUTPUT);
   pinMode(LIGHT_LIVING, OUTPUT);
   pinMode(LIGHT_BEDROOM, OUTPUT);
   pinMode(LIGHT_RES, OUTPUT);
   pinMode(LIGHT_STUDY, OUTPUT);
   //舵机
   servo1.attach(servo1Pin, minUs, maxUs);
   servo1.write(pos);   
   WiFi.begin();
   Serial.println(F("Adafruit MQTT demo"));
   
   //可以添加多组wifi(你的WIFI名称和密码)
   wifiMulti.addAP("dueros", "dueros");
   wifiMulti.addAP("Develop", "ccbfudevelop");
   //连接成功返回IP
   Serial.println("Connecting Wifi...");
    if(wifiMulti.run() == WL_CONNECTED) {
        Serial.println("");
        Serial.println("WiFi connected");
        Serial.println("IP address: ");
        Serial.println(WiFi.localIP());
    }

    onoffbutton.setCallback(onoffcallback);
  // Setup MQTT subscription for time feed.
    mqtt.subscribe(&onoffbutton);
    // This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket
#if defined (__AVR_ATtiny85__)
  if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
#endif
  // End of trinket special code

  pixels.begin(); // This initializes the NeoPixel library.
  
}
bool sk;
void loop() {
  //检查wifi
  if(wifiMulti.run() != WL_CONNECTED) {
        Serial.println("WiFi not connected!");
        delay(1000);
    }
   
   if(wifiMulti.run() == WL_CONNECTED) {
   digitalWrite(LED_PIN,HIGH);
    }

   
  sk=MQTT_connect();

  //等待订阅消息和回调子循环(尽量在这里消耗时间)
  mqtt.processPackets(10000);
  
if(sk)
  {
     //ping 服务器来保持mqtt连接活性
     if(! mqtt.ping()) {
       Serial.println("lose mqqt connect!");
       mqtt.disconnect();
     }
  }
  //发送消息
  //if (! photocell.publish("{\"msg\":{\"type\": 1,\"dev_id\":4656519,\"ds_id\":\"controlmyhome\",\"value\":42}}")) {
  //  Serial.println(F("Failed"));
  //} else {
  //  Serial.println(F("OK!"));
  //}
}
// 功能连接和重新连接以MQTT服务器(应该在循环函数中调用,如果连接,它会很小心)
bool MQTT_connect() {
  int8_t ret;
  // Stop if already connected.
  if (mqtt.connected()) {
    return true;
  }
  Serial.println("ReConnecting to MQTT... ");
  if(mqtt.connect() == 0)
     Serial.println("MQTT Connected!");
  else
     return false;
  return true;
}[/mw_shl_code]
、演示视频


发表于 2018-5-30 20:04 | 显示全部楼层
小鹿小鹿:MLGB的,老子要罢工
发表于 2018-5-31 10:04 | 显示全部楼层
报名流程为到大赛官方网页【注册信息】栏提交基础信息,然后论坛发帖介绍项目,请到此处补全注册信息:http://imf.elecspark.com/maker/?Page=1   否则视为报名不成功
发表于 2018-6-7 13:23 | 显示全部楼层
开发者是成都的吗?
发表于 2018-6-7 13:27 | 显示全部楼层
为什么要,传到云平台,再回到ESP32,不能直接树莓派控制电器吗?
 楼主| 发表于 2018-6-15 12:22 | 显示全部楼层
ezwyj 发表于 2018-6-7 13:27
为什么要,传到云平台,再回到ESP32,不能直接树莓派控制电器吗?

树莓派要放在客厅中间些的地方,好采集语音,但你卧室的灯不能从树莓派拉根线过去控制吧,考虑到实际情况后才这样做的
发表于 2018-9-16 00:01 来自手机 | 显示全部楼层
可以交流吗
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 02:54 , Processed in 0.141240 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表