esp8266对接阿里云平台

esp8266对接阿里云平台

上面几篇博客已经介绍了esp8266的开发环境搭建和自己的工程创建问题,本文开始介绍这个wifi模块的应用—对接阿里云平台。后面再此基础上再优化其他功能。

1.创建自己的工程

sdk根目录创建project,后面将ESP8266_RTOS_SDK-3.1.1/examples/get-started/project_template拷贝到project中,后面就能够基于此项目做开发了。
可基于user_main.c开发。
在这里插入图片描述

2.修改代码

我们对接阿里云平台需要基于平台提供的mqtt example做开发,但是想要读懂代码,就必须要去了解下标准的mqtt协议是什么样的,我前面的文章有过介绍,后面其他功能都需要如此。
直接贴出我的代码:

/*
   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>

//#include "esp_system.h"

//#include "freertos/FreeRTOS.h"
//#include "freertos/task.h"

#include <stddef.h>
#include <stdio.h>
#include <string.h>

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

#include "MQTTClient.h"

#define  DBG_C(...) printf("[%s,%d]",__FUNCTION__,__LINE__);printf(__VA_ARGS__)


/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;

/* The event group allows multiple bits for each event,
   but we only care about one event - are we connected
   to the AP with an IP? */
const int CONNECTED_BIT = BIT0;

#define MQTT_CLIENT_THREAD_NAME         "mqtt_client_thread"
#define MQTT_CLIENT_THREAD_STACK_WORDS  4096
#define MQTT_CLIENT_THREAD_PRIO         8

static const char *TAG = "example";

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch (event->event_id) {
    case SYSTEM_EVENT_STA_START:
        DBG_C("SYSTEM_EVENT_STA_START\n");
        esp_wifi_connect();
        break;

    case SYSTEM_EVENT_STA_GOT_IP:
        DBG_C("SYSTEM_EVENT_STA_GOT_IP\n");
        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
        break;

    case SYSTEM_EVENT_STA_DISCONNECTED:
        /* This is a workaround as ESP32 WiFi libs don't currently
           auto-reassociate. */
        DBG_C("SYSTEM_EVENT_STA_DISCONNECTED\n");
        esp_wifi_connect();
        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
        break;

    default:
        break;
    }

    return ESP_OK;
}

static void initialise_wifi(void)
{
   //wifi相关初始化
    tcpip_adapter_init();
    wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    //设备需要连接wifi的ssid password配置
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "cmcc",
            .password = "qwe123456",
        },
    };
    DBG_C("Setting WiFi configuration SSID %s...\n", wifi_config.sta.ssid);
    //设置wifi 模式 station模式
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
    //开启wif,联网
    ESP_ERROR_CHECK(esp_wifi_start());
}

//mqtt 相关参数
#define CONFIG_MQTT_PAYLOAD_BUFFER    (1*1024)
#define CONFIG_MQTT_BROKER            "a1PC1yDIZoi.iot-as-mqtt.cn-shanghai.aliyuncs.com"   // "106.15.83.29"//
#define CONFIG_MQTT_PORT              1883
#define CONFIG_MQTT_SUB_TOPIC         "/a1PC1yDIZoi/hMdcyQzFeEa86qEwmOhf/user/report"
#define CONFIG_MQTT_PUB_TOPIC         "/a1PC1yDIZoi/hMdcyQzFeEa86qEwmOhf/user/data"

static void messageArrived(MessageData *data)
{
   char recvbuf[512];
   memset(recvbuf,0,512);
   strncpy(recvbuf,(char *)data->message->payload,data->message->payloadlen);
   DBG_C("Message arrived[%d]:%s\n", data->message->payloadlen, recvbuf);

}


//publish
static int mqtt_client_send(MQTTClient * client)
{
      MQTTMessage message;
      static uint32_t count = 0;
      char payload[512];
      int rc=0;
      
      memset(payload,0,512);
      message.qos = QOS0;
      message.retained = 0;
      message.payload = payload;
      sprintf(payload, "message number %d", ++count);
      message.payloadlen = strlen(payload);

      if((count%50) != 10)
         return 0;

      if ((rc = MQTTPublish(client, CONFIG_MQTT_PUB_TOPIC, &message)) != 0) {
          DBG_C("Return code from MQTT publish is %d\n", rc);
      } else {
          DBG_C("MQTT published topic %s, len:%u heap:%u\n", CONFIG_MQTT_PUB_TOPIC, message.payloadlen, esp_get_free_heap_size());
      }
    
      if (rc != 0) {
          return -1;
      }

      return rc;
}



static void mqtt_client_thread(void *pvParameters)
{
    char *payload = NULL;
    MQTTClient client;
    Network network;
    int rc = 0;
    char clientID[32] = {0};



    MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
  
    //mqtt 网络连接初始化
    NetworkInit(&network);

    if (MQTTClientInit(&client, &network, 0, NULL, 0, NULL, 0) == false) {
        DBG_C("mqtt init err\n");
        vTaskDelete(NULL);
    }

    payload = malloc(CONFIG_MQTT_PAYLOAD_BUFFER);

    if (!payload) {
        DBG_C("mqtt malloc err\n");
    } else {
        memset(payload, 0x0, CONFIG_MQTT_PAYLOAD_BUFFER);
    }

    for (;;) {
        DBG_C("wait wifi connect...\n");
        xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
        //创建mqtt socket连接,底层就是调用socket connect 
        if ((rc = NetworkConnect(&network, CONFIG_MQTT_BROKER, CONFIG_MQTT_PORT)) != 0) {
            DBG_C("Return code from network connect is %d\n", rc);
            continue;
        }

        connectData.MQTTVersion = 3;

        //sprintf(clientID, "%s_%u", CONFIG_MQTT_CLIENT_ID, esp_random());

        connectData.clientID.cstring = "a1PC1yDIZoi.hMdcyQzFeEa86qEwmOhf|securemode=3,timestamp=2524608000000,signmethod=hmacsha1,gw=0,ext=0,partner_id=example.demo.partner-id,module_id=example.demo.module-id|";
        connectData.keepAliveInterval = 60;

        connectData.username.cstring = "hMdcyQzFeEa86qEwmOhf&a1PC1yDIZoi";
        connectData.password.cstring = "92ee26a897d43004c78e94065d6209d5f8ba1c65";

        connectData.cleansession = 0;

        DBG_C("MQTT Connecting\n");
        //建立mqtt连接
        if ((rc = MQTTConnect(&client, &connectData)) != 0) {
            DBG_C("Return code from MQTT connect is %d\n", rc);
            network.disconnect(&network);
            continue;
        }

        DBG_C("MQTT Connected\n");

        //订阅CONFIG_MQTT_SUB_TOPIC topic
        rc = MQTTSubscribe(&client, CONFIG_MQTT_SUB_TOPIC, QOS0, messageArrived);
        MQTTYield(&client, 500);
        if (rc != 0) {
            printf("[%s] Client Subscribed %d\n",__func__, rc);
            goto fail;
        }
        DBG_C("MQTT subscribe to topic %s OK\n", CONFIG_MQTT_SUB_TOPIC);


        int timeout_count = 0;
        while (timeout_count < 10 ) {
            rc = MQTTYield(&client, 500);
        
            if(client.isconnected == 0)
            {
                printf("[%s]mqtt has disconnect!\n",__func__);
                break;
            }

            if(client.ping_outstanding == 1)    //pin失败!
            {
                timeout_count++;
                printf("[%s]rc=%d, c->ping_outstanding=%d\r\n",__func__,rc, client.ping_outstanding);
            }else{
                timeout_count = 0;
            }

//            rc = mqtt_client_send(&client);
//            if(rc < 0)
//                DBG_C("MQTT PUBLISH FAIL\n");
            vTaskDelay(100);
        }

    }
fail:
    network.disconnect(&network);
    DBG_C("mqtt_client_thread going to be deleted\n");
    vTaskDelete(NULL);
    return;
}



/******************************************************************************
 * FunctionName : app_main
 * Description  : entry of user application, init user function here
 * Parameters   : none
 * Returns      : none
*******************************************************************************/
void app_main(void)
{
   //modify cdb 2019-12-19
   // printf("SDK version:%s\n", esp_get_idf_version());
   // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    int i=0;
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    ESP_ERROR_CHECK(ret);
    //初始化wifi,并且联网
    initialise_wifi();
    //创建mqtt 处理线程
    ret = xTaskCreate(&mqtt_client_thread,
                  MQTT_CLIENT_THREAD_NAME,
                  MQTT_CLIENT_THREAD_STACK_WORDS,
                  NULL,
                  MQTT_CLIENT_THREAD_PRIO,
                  NULL);

    if (ret != pdPASS)  {
        DBG_C("mqtt create client thread %s failed\n", MQTT_CLIENT_THREAD_NAME);
    }
    //程序将进入死循环,防止退出,并且打印运行时间log,单位s,也开启rtos任务调度
    while(1)
    {
        DBG_C("the system is runing,runtime:%d\n",i++);
        vTaskDelay(1000 / portTICK_RATE_MS);
    }

}

现在只是实现mqtt连接阿里云平台功能,其相关参数暂时固化到代码中,比如wifi相关信息,阿里云平台三元组等信息,后面可以优化代码将相关参数写入到flash中保存,如果有nv参数,通过写nv也是可以的,这个后面再做优化,写入方式可以通过at命令实现。

3.编译

make clean
make
最后会在工程目录build目录生成相关bin文件(见下图)。
在这里插入图片描述

4.烧录

使用其提供的烧录工具烧录(注意地址)。
在这里插入图片描述

5.按reset服务运行

下面是开机log,以及设备连上网络和服务后我通过阿里云平台下发hello world的log,下发的topic为 /a1PC1yDIZoi/hMdcyQzFeEa86qEwmOhf/user/report


 ets Jan  8 2013,rst cause:2, boot mode:(3,7)

load 0x40100000, len 7196, room 16 
tail 12
chksum 0x1d
ho 0 tail 12 room 4
load 0x3ffe8408, len 24, room 12 
tail 12
chksum 0x94
ho 0 tail 12 room 4
load 0x3ffe8420, len 3524, room 12 
tail 8
chksum 0xfb
csum 0xfbI (47) boot: ESP-IDF  2nd stage bootloader
I (47) boot: compile time 16:57:54
I (47) qio_mode: Enabling default flash chip QIO
I (53) boot: SPI Speed      : 40MHz
I (59) boot: SPI Mode       : QIO
I (65) boot: SPI Flash Size : 2MB
I (71) boot: Partition Table:
I (77) boot: ## Label            Usage          Type ST Offset   Length
I (88) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (100) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (111) boot:  2 factory          factory app      00 00 00010000 000f0000
I (123) boot: End of partition table
I (129) esp_image: segment 0: paddr=0x00010010 vaddr=0x40210010 size=0x49888 (301192) map
I (244) esp_image: segment 1: paddr=0x000598a0 vaddr=0x3ffe8000 size=0x00628 (  1576) load
I (245) esp_image: segment 2: paddr=0x00059ed0 vaddr=0x3ffe8628 size=0x0011c (   284) load
I (255) esp_image: segment 3: paddr=0x00059ff4 vaddr=0x40100000 size=0x075e0 ( 30176) load
I (278) boot: Loaded app from partition at offset 0x10000
I (301) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (311) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
,?[0;32mI (511) reset_reason: RTC reset 2 wakeup 0 store 0, reason is 2
[initialise_wifi,91]Setting WiFi configuration SSID cmcc...
mode : sta(2c:f4:32:77:3c:b6)
add if0
[app_main,283]the system is runing,runtime:0
[event_handler,53]SYSTEM_EVENT_STA_START   //开始联网
[mqtt_client_thread,175]wait wifi connect...
[app_main,283]the system is runing,runtime:1
[app_main,283]the system is runing,runtime:2
scandone
state: 0 -> 2 (b0)
[app_main,283]the system is runing,runtime:3
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 9
pm open phy_2,type:2 0 0
cnt 

connected with cmcc, channel 1
[app_main,283]the system is runing,runtime:4
I (6321) event: sta ip: 192.168.43.234, mask: 255.255.255.0, gw: 192.168.43.214  //联网成功,获取到ip地址
[event_handler,58]SYSTEM_EVENT_STA_GOT_IP
network connect 1
network connect 2
lwip_socket(PF_INET, SOCK_STREAM, 0) = 0
network connect 3
lwip_connect(0, addr=106.15.83.29 port=1883)
lwip_connect(0) succeeded
network connect 4
[mqtt_client_thread,195]MQTT Connecting     //mqtt连接
lwip_select(1, 0, 0x3fff4404, 0, tvsec=0 tvusec=30000000)
lwip_selscan: fd=0 ready for writing
lwip_select: nready=1
lwip_send(0, data=0x40107d80, size=262, flags=0x0)
lwip_send(0) err=0 written=262
lwip_select(1, 0x3fff4394, 0, 0, tvsec=0 tvusec=29980000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x40108840, 1, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0
lwip_recvfrom: netconn_recv err=0, netbuf=0x40108738
lwip_recvfrom: buflen=4 len=1 off=0 sock->lastoffset=0
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=1
lwip_recvfrom: lastdata now netbuf=0x40108738
lwip_select(1, 0x3fff4354, 0, 0, tvsec=0 tvusec=29830000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x3fff4380, 1, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0x40108738
lwip_recvfrom: buflen=4 len=1 off=0 sock->lastoffset=1
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=1
lwip_recvfrom: lastdata now netbuf=0x40108738
lwip_select(1, 0x3fff4394, 0, 0, tvsec=0 tvusec=29780000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x40108842, 2, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0x40108738
lwip_recvfrom: buflen=4 len=2 off=0 sock->lastoffset=2
lwip_recvfrom(0): addr=[app_main,283]the system is runing,runtime:5
106.15.83.29 port=1883 len=2
lwip_recvfrom: deleting netbuf=0x40108738
########cycle packet type:2##########  //这个是mqtt connect ack值
[mqtt_client_thread,203]MQTT Connected  //mqtt连接成功
lwip_select(1, 0, 0x3fff4434, 0, tvsec=0 tvusec=30000000)
lwip_selscan: fd=0 ready for writing
lwip_select: nready=1
lwip_send(0, data=0x40107d80, size=52, flags=0x0)
lwip_send(0) err=0 written=52
lwip_select(1, 0x3fff43c4, 0, 0, tvsec=0 tvusec=29970000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x40108840, 1, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0
lwip_recvfrom: netconn_recv err=0, netbuf=0x40108738
lwip_recvfrom: buflen=5 len=1 off=0 sock->lastoffset=0
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=1
lwip_recvfrom: lastdata now netbuf=0x40108738
lwip_select(1, 0x3fff4384, 0, 0, tvsec=0 tvusec=29630000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x3fff43b0, 1, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0x40108738
lwip_recvfrom: buflen=5 len=1 off=0 sock->lastoffset=1
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=1
lwip_recvfrom: lastdata now netbuf=0x40108738
lwip_select(1, 0x3fff43c4, 0, 0, tvsec=0 tvusec=29580000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x40108842, 3, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0x40108738
lwip_recvfrom: buflen=5 len=3 off=0 sock->lastoffset=2
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=3
lwip_recvfrom: deleting netbuf=0x40108738
########cycle packet type:9##########   //subscribe  SUBACK
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:6
lwip_select: timeout expired
########cycle packet type:0########## 
[mqtt_client_thread,212]MQTT subscribe to topic /a1PC1yDIZoi/hMdcyQzFeEa86qEwmOhf/user/report OK    //订阅ok
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:7
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:8
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:9
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:10
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:11
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:12
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:13
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:14
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:15
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:16
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:17
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:18
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:19
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:20
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:21
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:22
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:23
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:24
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:25
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:26
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:27
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x40108840, 1, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0
lwip_recvfrom: netconn_recv err=0, netbuf=0x4010871c
lwip_recvfrom: buflen=60 len=1 off=0 sock->lastoffset=0
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=1
lwip_recvfrom: lastdata now netbuf=0x4010871c
lwip_select(1, 0x3fff4404, 0, 0, tvsec=0 tvusec=470000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x3fff4430, 1, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0x4010871c
lwip_recvfrom: buflen=60 len=1 off=0 sock->lastoffset=1
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=1
lwip_recvfrom: lastdata now netbuf=0x4010871c
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=420000)
lwip_selscan: fd=0 ready for reading
lwip_select: nready=1
lwip_recvfrom(0, 0x40108842, 58, 0x8, ..)
lwip_recvfrom: top while sock->lastdata=0x4010871c
lwip_recvfrom: buflen=60 len=58 off=0 sock->lastoffset=2
lwip_recvfrom(0): addr=106.15.83.29 port=1883 len=58
lwip_recvfrom: deleting netbuf=0x4010871c
########cycle packet type:3##########    //PUBLISH  从阿里云平台下发的hello world
[messageArrived,109]Message arrived[11]:hello world       //设备收到并且打印
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=360000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:28
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
[app_main,283]the system is runing,runtime:29
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:30
lwip_select(1, 0x3fff4444, 0, 0, tvsec=0 tvusec=500000)
lwip_select: timeout expired
########cycle packet type:0##########
[app_main,283]the system is runing,runtime:31

至此,使用esp8266 连接阿里云平台成功,并且成功收到订阅消息。

发布了73 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_38240926/article/details/103603080