个性化阅读
专注于IT技术分析

IoT开发入门:ESP8266 Arduino教程

本文概述

本ESP8266 Arduino教程的目的是熟悉在芯片上使用Arduino的嵌入式编程, 该芯片因其在IoT领域的可访问性和易用性而在制造商社区(和一般开发人员)中变得非常流行。本教程还通过非官方的” hack”使我们与Alexa交涉, 让Alexa在家里进行投标(此技术并非用于生产, 仅用于家庭演示)。在家尝试, 但不要在工作。

ESP8266 Arduino教程的抽象图形表示

这项技术的优点在于, 我们可以在自己的房屋中使用它, 使Alexa几乎可以使几乎所有用电运行的东西实现自动化。另外, 我们还可以深入了解Arduino的嵌入式编程, 这项技能如今在主流程序员中可能并不常见。最后, 我们开始使用流行的ESP8266芯片, 这是”自己动手”的最爱。这是一个了不起的小芯片, 具有运行各种功能的能力, 并且内置了该项目需要的内置Wifi芯片。这将使Alexa设备和芯片直接相互通信。

只是Alexa编程的一些背景知识:Alexa”技能”编程模型的工作方式如下:

Alexa的抽象图形表示
  • 你与Alexa交谈。
  • Alexa会将你的语音一直传回到亚马逊的云。
  • 语音命令被路由到Alexa”技能”(在亚马逊云中运行的程序)。

Alexa的”技能”接管了命令的处理;通常, 它会导致将响应发送回Alexa设备, 从而使用户对响应说一些话。对于Alexa IoT, 该命令将路由到亚马逊云上的”设备影子”, 最终导致响应发送到你家中的其他设备。我们正在绕过所有黑客攻击。我们想让Alexa设备直接与我们家庭内部的ESP8266芯片通信, 而无需将任何数据发送到云端并返回。我们希望Alexa仅在家庭wifi网络内部和内部直接向ESP8266发送请求。

我们的hack并不是真正的秘密。我们将使ESP8266″模仿” Wemo Belkin, 这是一种具有Amazon特殊许可的设备, 允许它直接与Alexa设备进行通信, 而绕开了上述所有Amazon Cloud通信。

我们的ESP8266假装是Wemo, 享有直接从Alexa接收命令的特权。

ESP8266 Arduino教程的基本计划

  • 在ESP8266上侦听Alexa设备, 以在本地wifi网络上发送探针以查找兼容的设备, 然后通过说”我是Wemo”来回应这些探针。
  • 一旦被Alexa设备信任, 请侦听来自该设备的其他命令。通过通过红外线发射器发送红外线代码并打开/关闭电视来处理它们。

硬体需求

硬件的抽象图形表示,包括Alexa塔和Arduino板

要完成本教程, 你将需要自己获得一些物品, 所有这些物品都很容易获得。

  • 任何Alexa设备。我已经使用Alexa Dot开发了本教程。本教程可以与Echo模拟器一起使用吗?它可能! (但是我还没有测试过)。如果你喜欢冒险(或节俭), 请尝试一下。 Alexa设备需要零花钱, 但是免费使用Echosim。
  • ESP8266芯片。在撰写本文时, 它们的成本仅为几美元。你可以在Ebay或几乎任何库存充足的硬件商店上购买它们。
  • IR(红外)二极管。你需要将其连接到ESP8266芯片, 这需要你自己做。对于这个项目, 我们只需要发送功能;我们不在乎接收IR。确保将二极管连接到GND并输出0, 本教程才能正常工作。 (如果你采用其他任何方式, 也可以, 但你还必须负责相应地修改教程代码。此链接可能会对你有所帮助。请注意, 由于ESP8266上使用的编号方式, 引脚0可能会被标记为” D3″。
  • 一个串行适配器, 一侧为USB(可插入你的开发计算机), 另一侧为ESP8266芯片。
  • 你知道用户名和密码的本地wifi网络。
ESP8266附在适配器上的照片,在Echo Dot旁边

ESP8266连接到适配器, 在Echo Dot旁边

ESP8266附带红外二极管的照片

带红外二极管的ESP8266

Arduino软件工具

  • Arduino IDE。有适用于所有主要操作系统的版本, 包括Windows。本教程是在Ubuntu版本上开发的, 但是我也已经在Windows上安装并使用了Arduino, 没有问题。
  • 用于Arduino的ESP8266开发库。
  • 司机。幸运的是, 适配器的驱动程序很可能是即插即用的, 因此不需要其他驱动程序。

设置一切

  • 安装Arduino IDE。
  • 使用Boards Manager安装ESP8266库。

要使用Boards Manager安装ESP8266库:

  • 在Arduino IDE中, 打开文件->首选项。
  • 在”其他委员会管理器URL”中输入以下URL:http://arduino.esp8266.com/stable/package_esp8266com_index.json
  • 点击确定
ESP8266 Arduino教程屏幕快照突出显示了Boards Manager URL字段

转到董事会管理器(工具->董事会:[当前董事会]->董事会管理器)。

屏幕截图突出显示了Boards Manager菜单项

在”过滤器”文本框中, 键入” ESP8266″。

现在, 你已经添加了其他板管理器, 应该会获得” esp8266″的条目。选择它, 然后单击”安装”。

ESP8266 Boards Manager的Arduino教程屏幕截图突出显示了安装过程
  • 请稍等-下载所有内容都需要一些时间。
  • 重新启动你的Arduino IDE。
  • 打开”工具”->”开发板”:这次, 向下滚动到” Generic ESP8266 Module”并选择它。
ESP8266 Arduino教程屏幕截图突出显示了通用ESP8266模块的菜单选项

添加第三方库

Arduino提供了许多不同的方法来将外部库添加到你的项目中, 或称其为” Sketch”。为了使事情尽可能简单, 在本教程中, 我们将以最快的速度进行说明, 即简单地复制文件夹。为了使本教程正常工作, 我们将需要添加两个外部库:IRemoteESP8266和https://github.com/me-no-dev/ESPAsyncTCP。

  • 在GitHub中的教程代码中, 找到”库”目录。
  • 在Arduino的根安装目录(例如C:\ Program Files \ Arduino)中, 找到”库”子目录。
  • 将IRemoteESP8266目录从教程的”库”目录复制到Arduino的”库”目录。
  • 将ESPAsyncTCP目录从教程的”库”目录复制到Arduino的”库”目录。
  • 重新启动Arduino IDE。

现在, 该项目中包含用于IR传输和异步TCP的库。

设定值

下图显示了典型设置, 这些设置适用于我和我的硬件, 但可能因每个用户而异。你可以尝试以下设置, 但有可能可能必须根据特定的芯片和适配器进行调整。例如, 我的是nodemcu, 所以我不得不将重置方法从” ck”(默认)更改为” nodemcu”。另外, 将”调试端口”设置为”串行”, 以便可以使用串行调试器。我的是非常典型的设置, 因此你可以将我的设置用作基础。我只是说如果你要弄乱它们以使编译和刷新过程正常工作, 请不要感到惊讶。

串行调试端口菜单选项的屏幕截图

使用ESP8266 Hello World验证设置

Arduino项目以.ino文件开头。 .ino文件定义了两个输入点:设置和循环。对于我们的” hello world”, 我们将在ESP8266上稍作点灯, 以验证我们的代码是否有效。

//SET TO MATCH YOUR HARDWARE 
#define SERIAL_BAUD_RATE    9600
#define LED_PIN 2

/*---------------------------------------*/
//Runs once, when device is powered on or code has just been flashed 
void setup()
{
    //if set wrong, your serial debugger will not be readable 
    Serial.begin(SERIAL_BAUD_RATE);
    pinMode(LED_PIN, OUTPUT);
}


/*---------------------------------------*/
//Runs constantly 
void loop()
{
	digitalWrite(LED_PIN, LOW); 
	delay(1000); 
	digitalWrite(LED_PIN, HIGH);
	delay(1000); 
}

编译并刷新代码

如果到目前为止的设置是正确的, 那么编译和刷新是简单的步骤。要编译而不闪烁, 只需从Arduino菜单转到Sketch-> Verify / Compile。

草图菜单中的上载菜单选项的屏幕截图

要将代码刷新到芯片上并进行编译, 请从Arduino菜单中选择Sketch-> Upload。

正在进行上传的屏幕截图

如果刷新成功, 你将看到进度显示从0%变为100%, 在此期间, 芯片上的LED最有可能实际上会闪烁或闪烁。

要测试串行调试是否有效:

  • 首先, 确保将调试端口设置为串行(工具->调试端口)。
  • 代码完成闪烁到芯片后, 选择工具->串行监视器。
串行调试端口菜单选项的屏幕截图

成功启动后, 串行调试器的输出:

成功启动后串行调试器输出的屏幕截图

太好了, 这样行得通;接下来, 我们要验证我们的IR输出。让我们通过红外发射器发送信号, 并验证信号是否通过。

我们将利用现有的Arduino IR库来帮助我们。 Arduino的一大优点是, 将库和模块捕捉进出非常容易。 C ++框架令人耳目一新!

只需按照Git repo的README文件中的说明进行操作即可在Arduino中安装。

此代码只是重复闪烁红外发射器。 IR是人眼看不见的, 但是有测试它的提示。运行此代码, 验证(通过调试器)该代码正在你的芯片上运行, 然后打开移动设备的相机。通过相机直接看红外二极管灯泡。如果它可以正常工作, 则应该可以看到灯泡已打开和关闭。你也可以使用任何可用的遥控器(例如, 标准电视遥控器)来尝试此操作。以下代码应使IR灯泡每0.5秒开始闪烁一次。实际上, 它会发送LG开/关命令, 因此实际上可能会在附近打开和关闭LG电视。

#include <IRremoteESP8266.h>    // IR Library


IRsend* irSend;                 // infrared sender

//SET TO MATCH YOUR HARDWARE 
#define SERIAL_BAUD_RATE    9600

//PIN 0 is D3 ON THE CHIP 
#define IR_PIN              0 

/*---------------------------------------*/
//Runs once, when device is powered on or code has just been flashed 
void setup()
{
    //if set wrong, your serial debugger will not be readable 
    Serial.begin(SERIAL_BAUD_RATE);

    //initialize the IR 
    irSend = new IRsend(IR_PIN, true);
    irSend->begin();
}

/*---------------------------------------*/
//Runs constantly 
void loop()
{
    irSend->sendNEC(0x20DF10EF, 32, 3);
    delay(1000);
}

开始ESP8266教程

如果到目前为止一切正常, 我认为我们对基本设备和设置都可以正常运行感到满意, 并且我们准备开始本教程。

连接到Wifi

首先, 我们需要连接到本地wifi。下面的代码将尝试连接到Wifi, 并报告连接成功(通过串行调试器)。在代码示例中, 请不要忘记用你的wifi网络的用户名替换myWifiSsid的值, 并使用正确的密码替换myWifiPassword的值。

#include "debug.h"              // Serial debugger printing
#include "WifiConnection.h"     // Wifi connection // this file is part of my tutorial code
#include <IRremoteESP8266.h>    // IR library 


WifiConnection* wifi;           // wifi connection
IRsend* irSend;                 // infrared sender


//SET YOUR WIFI CREDS 
const char* myWifiSsid = "***";
const char* myWifiPassword = "*******";

//SET TO MATCH YOUR HARDWARE 
#define SERIAL_BAUD_RATE    9600

//PIN 0 is D3 ON THE CHIP 
#define IR_PIN              0



/*---------------------------------------*/
//Runs once, when device is powered on or code has just been flashed 
void setup()
{
    //if set wrong, your serial debugger will not be readable 
    Serial.begin(SERIAL_BAUD_RATE);

    //initialize wifi connection 
    wifi = new WifiConnection(myWifiSsid, myWifiPassword);
    wifi->begin();

    //connect to wifi 
    if (wifi->connect())
    {
        debugPrint("Wifi Connected");
    }
}


/*---------------------------------------*/
//Runs constantly 
void loop()
{
}

运行Wemo服务器

连接的?好。现在, 我们来了解项目的重点:Wemo服务器。

我自己的Wemo模拟器包含在本教程的源文件中。现在, 你可以搜索Google并找到一个更简单的Wemo模拟器。你可以找到使用更少的代码编写的代码, 而且它易于理解。无论如何, 请随时进行检查, 试验, 编写自己的内容, 等等。这些都是使本教程成为你自己的一部分。

我的背后的原因是它使用了ESPAsyncTCP。为什么这样好?好吧, 在这种方法变得不可靠之前, 你可以使用ESP8266在ESP8266上运行的服务器(或设备)如此之多, 从某种意义上说, Alexa将开始丢失设备(找不到它们), 命令将被丢弃, 并且性能变慢。我发现通过使用ESPAsyncTCP库可以使此数字最大化。

如果没有它, 我发现在大约10到12台设备中爬入是不可靠的;有了它, 我发现该数字最多可以达到16。如果你想扩展本教程并探索芯片功能的极限, 我建议使用我的版本。如果你只是想了解一个简单的版本, 请随时在Google上搜索” wemo emulator Arduino”;你应该找到许多示例。

现在, 我们必须安装ESPAsyncTCP库。像安装IR库一样安装它;转到Git页面并按照说明进行操作。

该库也包含在我的esp8266 arduino示例代码中。这只是打开wifi连接, 侦听Alexa发现请求并通过返回”我是Wemo”响应进行处理的代码。

#include "debug.h"              // Serial debugger printing
#include "WifiConnection.h"     // Wifi connection 
#include "Wemulator.h"          // Our Wemo emulator 
#include <IRremoteESP8266.h>    // IR library 


WifiConnection* wifi;           // wifi connection
Wemulator* wemulator;           // wemo emulator
IRsend* irSend;                 // infrared sender


//SET YOUR WIFI CREDS 
const char* myWifiSsid = "***";
const char* myWifiPassword = "*******";

//SET TO MATCH YOUR HARDWARE 
#define SERIAL_BAUD_RATE    9600

//PIN 0 is D3 ON THE CHIP 
#define IR_PIN              0
    
    
/*---------------------------------------*/
//Runs once, when device is powered on or code has just been flashed 
void setup()
{
    //if set wrong, your serial debugger will not be readable 
    Serial.begin(SERIAL_BAUD_RATE);

    //initialize wifi connection 
    wifi = new WifiConnection(myWifiSsid, myWifiPassword);
    wifi->begin();

    //initialize the IR 
    irSend = new IRsend(IR_PIN, false);
    irSend->begin();

    //initialize wemo emulator 
    wemulator = new Wemulator();

    //connect to wifi 
    if (wifi->connect())
    {
        wemulator->begin(); 

        //start the wemo emulator (it runs as a series of webservers) 
        wemulator->addDevice("tv", new WemoCallbackHandler(&commandReceived));
        wemulator->addDevice("television", new WemoCallbackHandler(&commandReceived));
        wemulator->addDevice("my tv", new WemoCallbackHandler(&commandReceived));
        wemulator->addDevice("my television", new WemoCallbackHandler(&commandReceived));
    }
}


/*---------------------------------------*/
//Runs constantly 
void loop()
{
    //let the wemulator listen for voice commands 
    if (wifi->isConnected)
    {
        wemulator->listen();
    }
}

预测试

通过与Alexa一起运行, 测试目前为止的产品(WiFi和仿真器)。本教程假定你已在家里设置并安装了Alexa设备。

测试发现:

对Alexa说:” Alexa, 发现设备。”

这将导致Alexa在你的本地wifi网络上广播UDP请求, 扫描Wemos和其他兼容设备。该请求应在对wemulator-> listen()的调用中收到;在loop()函数中。依次将其路由到Wemulator的handleUDPPacket(*)方法。在nextUDPResponse()方法中发送响应。注意该响应的内容:

const char UDP_TEMPLATE[] PROGMEM =
    "HTTP/1.1 200 OK\r\n"
    "CACHE-CONTROL: max-age=86400\r\n"
    "DATE: Sun, 20 Nov 2016 00:00:00 GMT\r\n"
    "EXT:\r\n"
    "LOCATION: http://%s:%d/setup.xml\r\n"
    "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n"
    "01-NLS: %s\r\n"
    "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
    "ST: urn:Belkin:device:**\r\n"
    "USN: uuid:Socket-1_0-%s::urn:Belkin:device:**\r\n\r\n";

这是告诉Alexa”我是Wemo(贝尔金)的代码, 我该如何帮助你?”的代码Alexa收到此响应后, 便会知道并记住将来的智能家居命令可能会路由到此设备。

此时串行调试器的输出应如下图所示。完成发现后, Alexa会口头告诉你它已在网络上”发现[N]个设备”。

Alexa输出的屏幕截图

在setup()函数中, 请注意以下代码段:

    new WemoCallbackHandler(&commandReceived)

这是我们将从Alexa捕获命令的回调。它的主体在WemoCallbackHandler.h(WemoCallbackHandler :: handleCallback)中定义。从Alexa捕获命令后, 就可以使用它来完成所需的操作。在此之前的几行中, 我们使用以下代码行设置了可以使用的命令:

    wemulator->addDevice("tv"); 
    wemulator->addDevice("television"); 
    wemulator->addDevice("my tv"); 
    wemulator->addDevice("my television");

因此, 这些是我们在芯片上运行的4个独立的”服务器”或侦听器。这样可以设置对Alexa说以下任何命令的能力:

Alexa, 打开电视Alexa, 关闭电视Alexa, 打开电视Alexa, 关闭电视Alexa, 打开我的电视Alexa, 关闭我的电视Alexa, 打开我的电视Alexa, 关闭我的电视

这就是我们将对其进行测试的方式。我们希望说任何这些命令都应该唤醒我们的代码并输入该回调, 我们可以在其中执行所需的操作。

说命令时会发生什么的屏幕截图

添加IR命令

现在我们已经收到命令, 现在该是通过打开/关闭电视来处理它了。因此, 这一切-wifi, wemo仿真器和IR-都将放在一起。我的电视是LG, 因此我查找了打开/关闭的适当顺序, 并通过IR库的sendNEC功能(LG使用NEC协议)发送了该顺序。 IR编码/解码本身就是一个单独的主题, 其中消息是通过信号调制来编码的;这是非常精确的时间, 标记和空格的规范。每个制造商都倾向于使用其自己的专有协议来执行命令, 并且时序不同。这非常有趣, 你可以通过查看该IR库的源代码, 进行谷歌搜索等来进行更深入的研究。但是, 为了方便起见, IR库为我们提供了所有这些细节。

你的电视不是LG吗?只是谷歌正确的代码。这是Sony电视的命令(注意:未经测试):

irSend.sendSony(0xa90, 12);

如果你想自己动手做, 可以设置一个IR接收器, 将遥控器(或任何IR发射器)对准它, 并解码其发送的代码;但是, 那是另一本教程。

端到端测试

  • 将Alexa放在可以听到你声音的任何地方。
  • 将ESP8266和连接的红外二极管放在电视的遥控范围内。
  • 说” Alexa, 发现设备”。等待它报告成功(它应该已经发现至少一台设备)。
  • 说” Alexa, 打开电视”或” Alexa, 关闭电视”。

Alexa应该理解你的命令(作为智能家居命令, 不针对特定技能), 搜索用于处理该设备的本地设备, 然后将该命令发送到该设备(你的ESP8266)。你的设备应将其接收并将遥控器命令发送到电视。你可以通过手机摄像头查看二极管, 以确保其发光。

由于关闭电视的IR代码与打开电视的代码相同, 因此你发出命令”打开”还是”关闭”都没有关系。相同的代码, 并切换状态。如果电视关闭, 则应打开电视;如果电视打开, 则应关闭电视。

故障排除

你已连接到Wifi吗?

你是否在正确的变量值中输入了正确的用户名/密码?

//SET YOUR WIFI CREDS 
const char* myWifiSsid = "***";
const char* myWifiPassword = "*******";

连接到Wifi时, 是否通过串行调试端口收到故障消息或任何错误?

你的Wifi是否已打开, 并且可以通过任何其他常规方式连接到它吗?

Alexa是否发现了你的设备?

当你说” Alexa, 发现设备”时, Alexa将发出发现设备的请求。

必须正确配置和设置Alexa, 并将其连接到与ESP8266相同的Wifi网络。

在Fauxmo.h中查看。参见函数Fauxmo :: handle()。这是ESP8266听到呼叫后将运行的第一个代码。放入调试消息以查看之后是否有任何代码

  if (len > 0) 
  { 

在跑。如果不是, 则说明没有收到命令。如果是, 则似乎正在接收命令, 但未正确处理。从那里按照代码查找问题所在。

你的网络上还有许多其他可发现的设备吗?太多会导致发现运行速度变慢, 甚至有时会失败。

你的设备收到命令了吗?

当你发出命令” Alexa, 打开电视”时, 执行应输入WemoCallbackHandler :: handleCallback处理程序(在WemoCallbackHandler.h文件中)。如果尚未执行此操作, 请尝试在其中输出一些调试消息, 以确保在发出命令时将其触发。另外, 在发出命令之前, 尝试说” Alexa, 发现设备”, 以确保Alexa知道你的设备。此步骤假定设备发现成功。

红外二极管发光吗?

如前所述, 当你认为设备应该发光时, 请将手机的摄像头对准它, 然后通过摄像头查看二极管。尽管在现实生活中你什么都看不到, 但是通过相机, 它应该像正常的灯光一样亮起并闪烁。如果看到此消息, 则表明它正在发射……某物。

红外信号是否反向?

你的IR二极管的接线方式可以使信号实质上反向。请谅解我, 因为我不是电子产品或接线人员, 但错误地连接二极管的结果将是默认情况下IR灯将打开, 但当IRSend库打算打开时将其关闭。它在。在这种情况下, 默认情况下, 在setup()代码运行之后, 但在其他任何事情发生之前, 你的红外灯应处于打开状态(可通过相机看到)。如果要注释掉loop()内的所有代码, 则应该看到它一直处于打开状态。

要更清楚地了解如何解决此问题, 请进入教程代码的library / IRemoteESP8266 / src文件夹。参见构造函数:

IRsend::IRsend(uint16_t IRsendPin, bool inverted) : IRpin(IRsendPin), periodOffset(PERIOD_OFFSET)
{
    if (inverted)
    {
        outputOn = LOW;
        outputOff = HIGH;
    }
    else
    {
        outputOn = HIGH;
        outputOff = LOW;
    }
}

我们所说的是”反向”参数和处理它的逻辑。如果你的接线颠倒了, 最简单的解决方案是在代码中做些小的改动以允许这样做(而不是重新接线……但是如果你愿意, 可以这样做)。只需在AlexaTvRemote.ino中更改此行:

    //initialize the IR 
    irSend = new IRsend(IR_PIN, false);

to

    //initialize the IR 
    irSend = new IRsend(IR_PIN, true);

你是否拥有正确的遥控器代码和命令?

如果其他一切似乎都没问题, 但电视没有服从, 则IR代码很可能有问题。在该IR库接口上尝试其他函数调用(例如sendLG, sendPanasonic, sendSharp等), 或确保你使用的函数与硬件相匹配。该库不太可能不支持你电视的硬件, 但我认为这在技术上是可行的。

确保你要发送的代码适合你的硬件。你可能需要在Google上进行一些挖掘才能找到合适的一个。如果所有其他方法均失败, 则始终可以选择在按下”电源”按钮时检测从工作遥控器发出的代码, 但这是不同的教程, 并且需要不同的硬件。

本文总结

希望一切都能为你解决。如果是这样(甚至可能不是这样), 那么这是一次在多个对象上同时割牙的好方法:

  • Alexa
  • 嵌入式编程
  • ESP8266芯片
  • Arduino IDE

当然, 当然, 你还可以通过语音命令打开/关闭电视, 这可能会带来一点便利。

为什么要骇客?

为什么这是黑客行为, 而不是Alexa基本API的一部分?在学习了如何发展我的第一个Alexa技能之后, 我真正想知道的就是”如何直接从Alexa向网络上的另一台设备发送命令?”令人遗憾的是, 亚马逊没有公开用于在Alexa设备与本地网络上的其他对象之间进行通信的完整API, 而没有经历”技能”或”智能家居”范式(其中所有内容都必须发送到AWS)在做任何事情之前), 但他们只是没有。

尝试进一步

尝试使用一套远程控制命令来更全面地控制电视, 例如更改频道和控制音量。通过查看你可以在一个ESP8266上监听多少个不同的命令来测试芯片的极限(提示:该数字几乎不破两位数, 无需进行一些非常聪明的编程)。如果你擅长使用硬件, 请尝试将其他设备直接连接到ESP8266芯片, 而不是通过IR控制其他设备;喜欢照明之类的。重塑wemo!

有关:

  • 我如何制作功能齐全的Arduino气象站
  • 使用ESP32音频采样
赞(0)
未经允许不得转载:srcmini » IoT开发入门:ESP8266 Arduino教程

评论 抢沙发

评论前必须登录!