ESP32与Xbox手柄的UART通信测试,基于Arduino框架和pyserial+pygame

这篇具有很好参考价值的文章主要介绍了ESP32与Xbox手柄的UART通信测试,基于Arduino框架和pyserial+pygame。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 说明

这个项目的目标是实现使用手柄来控制ESP32。最近正在进行无人机项目,但是由于没有适合的遥控器来控制四轴,画板子也有些占用时间,所以比较有效的方法就是基于手头有的Xbox手柄来进行一个DIY,在手柄与ESP32之间建立串口通信。此处使用PC作为中继,可能速度有些慢,但是基于目前需求,速度已经足够了。下图说明了无人机项目的通信方式,红框部分为本次涉及部分。

ESP32与Xbox手柄的UART通信测试,基于Arduino框架和pyserial+pygame

2. 环境

这里我使用主要Ubuntu 18作为开发环境,Win10下也能正常运行。python版本为3.9,所需库为pygamepyserial

3. 手柄与PC之间的通信测试

手柄与PC之间通过Pygame建立通信,以下提供了两个测试程序,第一个测试程序是一个简单的终端输出,如果手柄工作正常,就会看到六轴的输出。

import pygame
import time

pygame.init()
pygame.joystick.init()
done=False

while (done != True):
    for event in pygame.event.get():  # User did something
        if event.type == pygame.QUIT:  # If user clicked close
            done = True  # Flag that we are done so we exit this loop
    joystick_count = pygame.joystick.get_count()
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()
        axes = joystick.get_numaxes()
        print('================')
        time.sleep(0.1)
        for i in range(axes):
            axis = joystick.get_axis(i)
            print(axis) 

以下测试程序提供了一个简单的GUI来对每个按键进行测试

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vi:ts=4 sw=4 et
#
# This tool runs fine on both Python 2 and Python 3.
#
# https://github.com/denilsonsa/pygame-joystick-test

from __future__ import division
from __future__ import print_function

import sys
import pygame
from pygame.locals import *


class joystick_handler(object):
    def __init__(self, id):
        self.id = id
        self.joy = pygame.joystick.Joystick(id)
        self.name = self.joy.get_name()
        self.joy.init()
        self.numaxes    = self.joy.get_numaxes()
        self.numballs   = self.joy.get_numballs()
        self.numbuttons = self.joy.get_numbuttons()
        self.numhats    = self.joy.get_numhats()

        self.axis = []
        for i in range(self.numaxes):
            self.axis.append(self.joy.get_axis(i))

        self.ball = []
        for i in range(self.numballs):
            self.ball.append(self.joy.get_ball(i))

        self.button = []
        for i in range(self.numbuttons):
            self.button.append(self.joy.get_button(i))

        self.hat = []
        for i in range(self.numhats):
            self.hat.append(self.joy.get_hat(i))


class input_test(object):
    class program:
        "Program metadata"
        name    = "Pygame Joystick Test"
        version = "0.2"
        author  = "Denilson Figueiredo de Sá Maia"
        nameversion = name + " " + version

    class default:
        "Program constants"
        fontnames = [
            # Bold, Italic, Font name
            (0, 0, "Bitstream Vera Sans Mono"),
            (0, 0, "DejaVu Sans Mono"),
            (0, 0, "Inconsolata"),
            (0, 0, "LucidaTypewriter"),
            (0, 0, "Lucida Typewriter"),
            (0, 0, "Terminus"),
            (0, 0, "Luxi Mono"),
            (1, 0, "Monospace"),
            (1, 0, "Courier New"),
            (1, 0, "Courier"),
        ]
        # TODO: Add a command-line parameter to change the size.
        # TODO: Maybe make this program flexible, let the window height define
        #       the actual font/circle size.
        fontsize     = 20
        circleheight = 10
        resolution   = (640, 480)

    def load_the_fucking_font(self):
        # The only reason for this function is that pygame can find a font
        # but gets an IOError when trying to load it... So I must manually
        # workaround that.

        # self.font = pygame.font.SysFont(self.default.fontnames, self.default.fontsize)
        for bold, italic, f in self.default.fontnames:
            try:
                filename = pygame.font.match_font(f, bold, italic)
                if filename:
                    self.font = pygame.font.Font(filename, self.default.fontsize)
                    # print("Successfully loaded font: %s (%s)" % (f, filename))
                    break
            except IOError as e:
                # print("Could not load font: %s (%s)" % (f, filename))
                pass
        else:
            self.font = pygame.font.Font(None, self.default.fontsize)
            # print("Loaded the default fallback font: %s" % pygame.font.get_default_font())

    def pre_render_circle_image(self):
        size = self.default.circleheight
        self.circle = pygame.surface.Surface((size,size))
        self.circle.fill(Color("magenta"))
        basecolor  = ( 63,  63,  63, 255)  # RGBA
        lightcolor = (255, 255, 255, 255)
        for i in range(size // 2, -1, -1):
            color = (
                lightcolor[0] + i * (basecolor[0] - lightcolor[0]) // (size // 2),
                lightcolor[1] + i * (basecolor[1] - lightcolor[1]) // (size // 2),
                lightcolor[2] + i * (basecolor[2] - lightcolor[2]) // (size // 2),
                255
            )
            pygame.draw.circle(
                self.circle,
                color,
                (int(size // 4 + i // 2) + 1, int(size // 4 + i // 2) + 1),
                i,
                0
            )
        self.circle.set_colorkey(Color("magenta"), RLEACCEL)

    def init(self):
        pygame.init()
        pygame.event.set_blocked((MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN))
        # I'm assuming Font module has been loaded correctly
        self.load_the_fucking_font()
        # self.fontheight = self.font.get_height()
        self.fontheight = self.font.get_linesize()
        self.background = Color("black")
        self.statictext = Color("#FFFFA0")
        self.dynamictext = Color("white")
        self.antialias = 1
        self.pre_render_circle_image()
        # self.clock = pygame.time.Clock()
        self.joycount = pygame.joystick.get_count()
        if self.joycount == 0:
            print("This program only works with at least one joystick plugged in. No joysticks were detected.")
            self.quit(1)
        self.joy = []
        for i in range(self.joycount):
            self.joy.append(joystick_handler(i))

        # Find out the best window size
        rec_height = max(
            5 + joy.numaxes + joy.numballs + joy.numhats + (joy.numbuttons + 9) // 10
            for joy in self.joy
        ) * self.fontheight
        rec_width = max(
            [self.font.size("W" * 13)[0]] +
            [self.font.size(joy.name)[0] for joy in self.joy]
        ) * self.joycount
        self.resolution = (rec_width, rec_height)

    def run(self):
        self.screen = pygame.display.set_mode(self.resolution, RESIZABLE)
        pygame.display.set_caption(self.program.nameversion)
        self.circle.convert()

        while True:
            for i in range(self.joycount):
                self.draw_joy(i)
            pygame.display.flip()
            # self.clock.tick(30)
            for event in [pygame.event.wait(), ] + pygame.event.get():
                # QUIT             none
                # ACTIVEEVENT      gain, state
                # KEYDOWN          unicode, key, mod
                # KEYUP            key, mod
                # MOUSEMOTION      pos, rel, buttons
                # MOUSEBUTTONUP    pos, button
                # MOUSEBUTTONDOWN  pos, button
                # JOYAXISMOTION    joy, axis, value
                # JOYBALLMOTION    joy, ball, rel
                # JOYHATMOTION     joy, hat, value
                # JOYBUTTONUP      joy, button
                # JOYBUTTONDOWN    joy, button
                # VIDEORESIZE      size, w, h
                # VIDEOEXPOSE      none
                # USEREVENT        code
                if event.type == QUIT:
                    self.quit()
                elif event.type == KEYDOWN and event.key in [K_ESCAPE, K_q]:
                    self.quit()
                elif event.type == VIDEORESIZE:
                    self.screen = pygame.display.set_mode(event.size, RESIZABLE)
                elif event.type == JOYAXISMOTION:
                    self.joy[event.joy].axis[event.axis] = event.value
                elif event.type == JOYBALLMOTION:
                    self.joy[event.joy].ball[event.ball] = event.rel
                elif event.type == JOYHATMOTION:
                    self.joy[event.joy].hat[event.hat] = event.value
                elif event.type == JOYBUTTONUP:
                    self.joy[event.joy].button[event.button] = 0
                elif event.type == JOYBUTTONDOWN:
                    self.joy[event.joy].button[event.button] = 1

    def rendertextline(self, text, pos, color, linenumber=0):
        self.screen.blit(
            self.font.render(text, self.antialias, color, self.background),
            (pos[0], pos[1] + linenumber * self.fontheight)
            # I can access top-left coordinates of a Rect by indexes 0 and 1
        )

    def draw_slider(self, value, pos):
        width  = pos[2]
        height = self.default.circleheight
        left   = pos[0]
        top    = pos[1] + (pos[3] - height) // 2
        self.screen.fill(
            (127, 127, 127, 255),
            (left + height // 2, top + height // 2 - 2, width - height, 2)
        )
        self.screen.fill(
            (191, 191, 191, 255),
            (left + height // 2, top + height // 2, width - height, 2)
        )
        self.screen.fill(
            (127, 127, 127, 255),
            (left + height // 2, top + height // 2 - 2, 1, 2)
        )
        self.screen.fill(
            (191, 191, 191, 255),
            (left + height // 2 + width - height - 1, top + height // 2 - 2, 1, 2)
        )
        self.screen.blit(
            self.circle,
            (left + (width - height) * (value + 1) // 2, top)
        )

    def draw_hat(self, value, pos):
        xvalue =  value[0] + 1
        yvalue = -value[1] + 1
        width  = min(pos[2], pos[3])
        height = min(pos[2], pos[3])
        left   = pos[0] + (pos[2] - width ) // 2
        top    = pos[1] + (pos[3] - height) // 2
        self.screen.fill((127, 127, 127, 255), (left, top              , width, 1))
        self.screen.fill((127, 127, 127, 255), (left, top + height // 2, width, 1))
        self.screen.fill((127, 127, 127, 255), (left, top + height  - 1, width, 1))
        self.screen.fill((127, 127, 127, 255), (left             , top, 1, height))
        self.screen.fill((127, 127, 127, 255), (left + width // 2, top, 1, height))
        self.screen.fill((127, 127, 127, 255), (left + width  - 1, top, 1, height))
        offx = xvalue * (width  - self.circle.get_width() ) // 2
        offy = yvalue * (height - self.circle.get_height()) // 2
        # self.screen.fill((255,255,255,255),(left + offx, top + offy) + self.circle.get_size())
        self.screen.blit(self.circle, (left + offx, top + offy))

    def draw_joy(self, joyid):
        joy = self.joy[joyid]
        width = self.screen.get_width() // self.joycount
        height = self.screen.get_height()
        pos = Rect(width * joyid, 0, width, height)
        self.screen.fill(self.background, pos)

        # This is the number of lines required for printing info about this joystick.
        # self.numlines = 5 + joy.numaxes + joy.numballs + joy.numhats + (joy.numbuttons+9)//10

        # Joy name
        # 0 Axes:
        # -0.123456789
        # 0 Trackballs:
        # -0.123,-0.123
        # 0 Hats:
        # -1,-1
        # 00 Buttons:
        # 0123456789

        # Note: the first character is the color of the text.
        text_colors = {
            "D": self.dynamictext,
            "S": self.statictext,
        }
        output_strings = [
            "S%s"             % joy.name,
            "S%d axes:"       % joy.numaxes
        ]+[ "D    %d=% .3f"   % (i, v) for i, v in enumerate(joy.axis) ]+[
            "S%d trackballs:" % joy.numballs
        ]+[ "D%d=% .2f,% .2f" % (i, v[0], v[1]) for i, v in enumerate(joy.ball) ]+[
            "S%d hats:"       % joy.numhats
        ]+[ "D  %d=% d,% d"   % (i, v[0], v[1]) for i, v in enumerate(joy.hat ) ]+[
            "S%d buttons:"    % joy.numbuttons
        ]
        for l in range(joy.numbuttons // 10 + 1):
            s = []
            for i in range(l * 10, min((l + 1) * 10, joy.numbuttons)):
                if joy.button[i]:
                    s.append("%d" % (i % 10))
                else:
                    s.append(" ")
            output_strings.append("D" + "".join(s))

        for i, line in enumerate(output_strings):
            color = text_colors[line[0]]
            self.rendertextline(line[1:], pos, color, linenumber=i)

        tmpwidth = self.font.size("    ")[0]
        for i, v in enumerate(joy.axis):
            self.draw_slider(
                v,
                (
                    pos[0],
                    pos[1] + (2 + i) * self.fontheight,
                    tmpwidth,
                    self.fontheight
                )
            )

        tmpwidth = self.font.size("  ")[0]
        for i, v in enumerate(joy.hat):
            self.draw_hat(
                v,
                (
                    pos[0],
                    pos[1] + (4 + joy.numaxes + joy.numballs + i) * self.fontheight,
                    tmpwidth,
                    self.fontheight
                )
            )
        # self.draw_hat((int(joy.axis[3]),int(joy.axis[4])), (pos[0], pos[1] + (4+joy.numaxes+joy.numballs+0)*self.fontheight, tmpwidth, self.fontheight))

    def quit(self, status=0):
        pygame.quit()
        sys.exit(status)


if __name__ == "__main__":
    program = input_test()
    program.init()
    program.run()  # This function should never return

如果环境没有问题,就会看到如下的GUI界面。这时候按动手柄上的相关按键,就能看到数值的实时更新。

ESP32与Xbox手柄的UART通信测试,基于Arduino框架和pyserial+pygame

4. python与ESP32的通信测试

接下来测试python与Esp32之间的通信。 这里在PC上直接使用pyserial 库,

import serial 
import time

ser = serial.Serial(
    port='/dev/ttyUSB0',
    baudrate=9600,
    parity=serial.PARITY_ODD,
    stopbits=serial.STOPBITS_TWO,
    bytesize=serial.SEVENBITS
)

ser.isOpen()

# read a string
while 1:
    trans_data = "Hello World"
    ser.write(trans_data.encode('utf-8')) # write a string
    received_data = ser.readline().decode() # read a byte

    print(received_data)


ESP32使用Arduino框架,,只需要使用串口就可以了。程序的逻辑也非常简单, 就是读取上位机发送的信息,并返回信息。

#include <Arduino.h>

String received_data;

void ReadData(void)
{
    if ( Serial.available() ) {
        received_data = Serial.readString();
    }
}
void setup() {
    Serial.begin(9600);
}

void loop() {
    ReadData();
    Serial.println(received_data);
    delay(200);
}

如果一切正常,那么输出如下:

Hello World
Hello World
Hello World
Hello World
Hello World
...

5. 手柄与ESP32的通信测试

如果以上两个的测试正常通过,我们接下来就可以测试手柄和ESP之间的通信了。这里我们的目标是使用UART发送给ESP32相应的手柄的值,并返回一个解码的值。

首先我们需要对原始数据进行一个处理,因为原始的手柄数据都是浮点值,为了方便esp的处理,我们在PC端就需要对原始数据进行一个处理,将其全部转换为整型。并将所有轴上的数据拼合成一个字符串来方便发送。

首先我们需要对上位机程序进行一个整合,上位机程序需要做的任务是读取手柄的值,然后转码并发送到ESP32,然后读取串口上的值。
程序如下,

import pygame
import time
import serial

class Joystick:
    def __init__(self):
        # initialization for joystick
        pygame.init()
        pygame.joystick.init()
        self.joystick_count = pygame.joystick.get_count()
        self.joystick = pygame.joystick.Joystick(0)
        self.joystick.init()
        self.axes = self.joystick.get_numaxes()
        self.joy_val = ""

        # initialization for serial
        self.ser = serial.Serial(
            port='/dev/ttyUSB0',
            baudrate=9600,
            timeout=1
        )
        self.ser.isOpen()
        self.done=False

    def JoystickRead(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.done = True

        self.joy_val = ""
        for i in range(self.axes):
            axis = int(round(self.joystick.get_axis(i), 2)*100) + 100
            axis_str = str(axis).zfill(3)
            self.joy_val = self.joy_val + axis_str

        #print("joy_val: ", self.joy_val)

    def SerialWriteAndRead(self):
            self.ser.write(self.joy_val.encode())
            # print(axis_str)
            received_data = self.ser.readline().decode()
            print(received_data, '\n')


if __name__ == '__main__':
    joystick = Joystick()
    while (joystick.done != True):
        joystick.JoystickRead()
        joystick.SerialWriteAndRead()
        time.sleep(0.05)
           

接下来对esp32的程序进行一个更改。在这里,esp32任务是读取上位机发来的手柄的值,然后解码。之后将解码后的值发送给上位机来验证手柄信息是否正常发送。相关代码如下,

#include <Arduino.h>

using namespace std;


String received_data;
string received_data_string;
string output_data;
char *p_data;

String default_data = "100100000100100000";

String joy_left_x;
String joy_left_y;
String joy_right_x;
String joy_right_y;
const int ktest= 100;


void ReadData(void)
{
    if ( Serial.available() ) {
        received_data = Serial.readString();
    }
    else {
        received_data = default_data;
    }
    delay(20);
}

void ProcessData(void)
{
    if (received_data.length() > 17)
    {
        joy_left_x = received_data.substring(0, 3);
        int left_x = joy_left_x.toInt();
        joy_left_y = received_data.substring(3, 6);
        int left_y = joy_left_y.toInt();
        joy_right_x = received_data.substring(6, 9);
        int right_x = joy_right_x.toInt();
        joy_right_y = received_data.substring(9, 12);
        int right_y = joy_right_y.toInt();
        delay(20);
    }
}

void ShowData(void)
{
    // Serial.print("Data received: \n");
    // Serial.println(received_data);
    // Serial.print("Joy Left X: \n");
    Serial.println(joy_left_x);
    Serial.println(joy_left_y);
    // Serial.println(joy_right_x);
    // Serial.println(joy_right_y);
}

void setup() {
    Serial.begin(9600);
}

void loop() {
    ReadData();
    ProcessData();
    ShowData();
    delay(20);
}

首先下载ESP32的程序,再运行上位机程序。如果正常输出,则会看到,

100

100

100

...

这是因为默认状态下,左摇杆的 x x x轴位置为0。为了方便进行串口通信,我们取到小数点后两位小数,并将其放大100倍。最后我们将其映射到0~200这个范围,这也是为什么输出为100的原因。

在调试过程中,需要注意的是UART的配置问题,如果配置有误,则会造成上位机输出信息错误。此外UART时序也是一个需要注意的问题。如果在调试过程中,发现结果不对,但是程序逻辑是正常的, 那么需要检查一下两侧的UART配置。文章来源地址https://www.toymoban.com/news/detail-404360.html

到了这里,关于ESP32与Xbox手柄的UART通信测试,基于Arduino框架和pyserial+pygame的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ESP32-CAM模块Arduino环境搭建测试

    一.ESP32+OV2640摄像头模块CameraWebServer视频查看 二.测试ESP32-CAM(后续称cam模块)代码是否上传执行成功测试 PS:上叙代码是测试cam模块代码是否执行成功 二.由于之前使用的是Arduino IDE 1.8.5版本,ESP32-CAM模块上传代码后报错 Camera probe failed with error 0x105(ESP_ERR_NOT_FOUND) Camera init failed wit

    2024年02月11日
    浏览(53)
  • ESP32 Arduino框架入门(二)外部中断和TouchPad(电容触摸引脚)

    接ESP32 Arduino框架入门(一)介绍和工程创建(8条消息) ESP32 Arduino框架入门(一)介绍和工程创建_长谷深风灯盏的博客-CSDN博客_arduino的esp32软件系统架构        接下来继续介绍ESP32-Wroom-32E的外部引脚、中断、定时器等部分内容         ESP32的外部引脚功能有ADC,GPIO,

    2024年02月10日
    浏览(39)
  • ESP32经典蓝牙和BLE的使用 (基于Arduino)

    经典蓝牙串口通信 该程序效果如下 ESP32完成初始化后生成一个蓝牙信号 手机通过蓝牙串口发送字符 B ,LED点亮 手机通过蓝牙串口发送字符 A ,LED熄灭 低功耗蓝牙 BLE (常用) BLE GATT协议 GATT全称Generic Attribute Profile, GATT 代表通用属性,它定义了暴露给连接的BLE设备的分层数据结

    2024年02月01日
    浏览(45)
  • ESP32在Arduino框架下采用SPI或8bit并口方式使用LVGL(v8.3)

    小相机,按一下能拍照,并将照片保存在sd卡中。 使用的时VSCode+PlatformIO进行开发(强力推荐,用了就回不去了),当然也可以使用ArduinoIDE进行开发。 具体怎么使用上述软件,网上有很多答案,可以自行查找。 ESP-IDF移植教程:点击这里 使用到的库: TFT_eSPI 和 lvgl 注: 8bi

    2024年02月05日
    浏览(51)
  • 【单片机】基于STM32的UART串口通信

    简单讲解一下UART通信协议,以及UART能够实现的一些功能,还有有关使用STM32CubeMX来配置芯片的一些操作。实验内容基于 正点原子精英板 开发板,单片机芯片为 STM32F103ZET6 。 在后面我会以我使用的STM32F429开发板来举例讲解(其他STM32系列芯片大多数都可以按照这些步骤来操作

    2024年01月17日
    浏览(69)
  • 基于 Arduino 库实现 ESP32 TCP Server 应用例程

    ESP32 开启 WiFi Station 模式连接路由器 连上路由器后将获取到分配的 IP 地址 基于分配的 IP 地址创建 TCP Server 手机与 ESP32 连接同一路由器 查看 UART0 日志打印,获取 TCP Server 的 IP 地址 使用手机端 TCP 调试 APP 与 ESP32 创建的 TCP Server 建立连接

    2024年02月12日
    浏览(56)
  • ESP32 直接使用WIFI进行UDP通信, 将光敏电阻传感器数值, 显示在OLED上 - 米思齐/Arduino

    ESP32主: 读取光敏传感器数值, 并通过UDP发送 ESP32从: 读取主机发送的数值, 并显示在OLED显示屏上 左边为ESP32, 连接OLED, 为STA模式从机 右边为ESP32, 连接光敏传感器, 为AP+STA主机, 可连接外网和连接从机

    2024年02月13日
    浏览(165)
  • Proteus平台下基于Arduino的通过UART串口可靠通信系统仿真、传感器数据采集、以及LCD屏幕二级菜单功能实现(附工程源码、设计报告)

    三个按键控制菜单,功能分别为:选择功能1,选择功能2,以及返回上一级; 通过三个外部中断对页面状态参量进行控制: 停止等待 当U1收到U2的ACK后才会发送下一次传感器采集到的数据 超时重传 当关闭U2后,U1到达设定的超时时间后,进行重传操作,直到收到U2的确认收到

    2024年02月16日
    浏览(44)
  • 基于树莓派4B与STM32的UART串口通信实验(代码开源)

    前言: 本文为手把手教学 树莓派4B与 STM32 的 UART 通讯 ,本次项目采用 树莓派4B 与  STM32  进行串口通讯,将彼此的数据进行互相传输。本篇博客同时提供了基于 YOLOv5-Lite 的目标检测数据联动,即将 树莓派4B 检测到的信息发送至 STM32 ,后续可以通过这些信息进行各种需求上

    2024年02月16日
    浏览(38)
  • 基于 Arduino 库实现 ESP32 使能 WiFi AP + TCP Server 的应用

    ESP32 开启 WiFi AP 模式创建 WiFi 热点 定义 IP 地址 创建 TCP Server 使用手机连接 ESP32 创建的 AP 热点 查看 UART0 日志打印,获取 TCP Server 的 IP 地址 使用手机端 TCP 调试 APP( 网络助手 ) 与 ESP32 创建的 TCP Server 建立连接

    2024年02月12日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包