[香橙派开发系列]使用蓝牙和手机进行信息的交换

文章目录

  • 前言
  • 一、HC05蓝牙模块
    • 1.HC05概述
    • 2.HC05的连接图
    • 3.进入HC05的命令模式
    • 4.常用的AT指令
      • 4.1 检查AT是否上线
      • 4.2 重启模块
      • 4.3 获取软件版本号
      • 4.4 恢复默认状态
      • 4.5 获取蓝牙的名称
      • 4.6 设置蓝牙模块的波特率
      • 4.7 查询蓝牙的连接模式
      • 4.8 查询模块角色
    • 5.连接电脑
    • 6.通过HC05发送消息
    • 7.stm32完整代码
  • 二、香橙派串口通讯
    • 1.打开串口设备
    • 2.接线
    • 3.串口函数
      • 3.1 打开串口文件
      • 3.2 关闭串口文件
      • 3.3 发送一个字符
      • 3.4 发送一个规定好的字符串
      • 3.5 printf
      • 3.6 返回等待读取的字符数
      • 3.7 读取字符
      • 3.8 缓冲区函数
    • 4.输出内容
    • 5.接收内容
    • 6.接收字符串
  • 三、使用hc05连接香橙派
    • 1.通过蓝牙发送字符串给手机
    • 2.手机发送内容给香橙派
  • 总结

前言

隔了这么久我准备再玩一下香橙派,最近这段时间还是比较的忙,我搭建了个论坛和博客,经常被网络攻击,所以我也是一直在弄网络去了,然后今天比较空闲就想着把单子做一下,这个单子需要使用到HC05蓝牙模块,所以我准备写一篇博客来使用香橙派控制HC05蓝牙模块。

一、HC05蓝牙模块

这个模块是非常经典的一个蓝牙模块,我之前有一单也是使用到这个模块,可以看一下我的这个视频【作息控制系统使用说明】,这个项目也是使用到蓝牙模块的,只不过当时时间紧任务重所以就没有写一篇博客来好好记录一下这个项目,等后面我会单独拿一个模块来介绍一下我做的这些小项目。

1.HC05概述

HC05是嵌入式蓝牙串口通讯模块,也就是使用串口就可以和HC05进行通讯并发送信息给蓝牙的接收端,这个模块有两种工作方式:

1 命令响应工作模式

2 自动连接工作模式

在自动连接的工作模式下又可以分为主、从、回环这三种模式,当传输数据时,根据事先设定的方式连接并传输数据。

在命令模式下,用户可以使用串口连接模块,并发送AT指令对模块进行设置。

2.HC05的连接图

这里我直接使用TTL to USB进行连接,因为我要设置一下这个模块的一些传输和内容。

img

这里和串口连接的模式一样

HC05TTL
3.3VCC
TXDRXD
RXDTXD
GNDGND

3.进入HC05的命令模式

首先我们需要通过AT指令来设置模块的一些内容,然后我们才好进入下面的一些操作,首先在这个模块上有一个按钮

img

在上电之前需要长按这个按键,然后再上电就可以进入命令模式,在进入命令模式后,模块上的LED等会缓慢的闪烁,如果没有进入就会闪得很快。

4.常用的AT指令

AT指令列表已经烂大街了,所以这里不全部说明,我只拿出一些常用的来说明即可。

4.1 检查AT是否上线

AT\r\n

如果模块在就会返回OK

img

4.2 重启模块

AT+RESET\r\n

如果执行成功就会返回OK,并进入自动连接模式

img

4.3 获取软件版本号

AT_VERSION?\r\n

执行完成后会返回版本信息和OK

img

4.4 恢复默认状态

AT+ORGL\r\n

执行完成后会返回OK,并将模块恢复为出厂设置。

4.5 获取蓝牙的名称

AT+NAME?\r\n

执行完成后会返回蓝牙名称并返回OK

img

4.6 设置蓝牙模块的波特率

AT+UART=波特率,停止位,校验位\r\n

这里我们设置一个比较常见的,波特率9600,停止位1位,无校验位

AT+UART=9600,1,0

这样就可以了。

4.7 查询蓝牙的连接模式

AT+CMODE?\r\n

返回当前模式,1代表任意模式,也就是蓝牙名称和蓝牙地址都可以连接,这里就不多说了,一般默认是1就可以了。

4.8 查询模块角色

AT+ROLE?

这里默认的是Slave从角色,也就是被动连接,不用动。

5.连接电脑

这里我踩了个大坑,因为我之前的设置是将波特率改为9600了,所以在串口的时候我就用9600进行连接,结果没有任何的反应,后面查了一下手册才发现它默认的波特率是38400,就很尴尬。

这里连接好电脑后先发送个AT指令来查看一下是否返回OK,如果没返回有可能是因为坏了或者是你没长按按键进入命令模式。

然后修改一下波特率,因为我们后面初始化串口不想给那么大的波特率

img

初始化完成后要记得重启一下模块,然后模块的波特率就设置好看,但是AT指令模式下还是那个波特率(又踩坑了,当不想改文章)。

6.通过HC05发送消息

这里我先用stm32来演示,因为我还在做单子所以先拿这个演示。

首先先写串口的初始化函数,这里我使用的是USART1,初始化代码就不展示了,因为今天的主题不是stm32,然后在main函数中我们使用重写的printf来通过串口发送数据。不知道的可以看我这篇博客异步通讯点灯。

int main(){
    OLED_Init();
    MX_Init_KEY();
    MX_Init_HC05();
	while(1){
        printf("hello\r\n");
        OLED_ShowString(1, 1, "hello");
	}
}

然后需要在手机上下载蓝牙调试宝,在应用商城都可以下载,这我就不给你们压缩包了,要不然我老喜欢挂码。

首先要把蓝牙模块连接到面包板上,然后就用手机连接好蓝牙

img

记住串口反接并且要接地即可。

然后打开蓝牙助手的界面

img

找到里面有一个HC-05,然后连接即可,第一次连接需要你输入一个配对码,这里输入1234或者0000都可以

img

然后给单片机上电后就会显示出hello来

img

学到这你已经会使用蓝牙进行发送信息了,stm32就先不再出现了,后面就是香橙派的内容了。

7.stm32完整代码

usart.c

#include "hc05.h"

void MX_Init_HC05(){
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    USART_InitTypeDef USART_InitStruct = {0};
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStruct);
    USART_Cmd(USART1, ENABLE);
}

int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);  
    USART1->DR = (u8) ch;      
	return ch;
}

二、香橙派串口通讯

在介绍使用香橙派使用HC05模块前先介绍一下串口通讯,并如何配置串口通讯。

1.打开串口设备

在香橙派中,串口通讯是使用设备来进行传输的, 所以我们需要在系统设置中将串口进行打开。

在打开之前先了解一下香橙派的串口引脚分布

img

在3b中有3个串口可供选择,然后通过

sudo orangepi-config

来开启设备,进入到设置页面

img

然后选择第一个,按一下回车即可,然后在下面的页面中选择Hardware

img

然后找到你要开启的串口

img

我这选择的是UART7,使用方向键移动到那后按一下空格就选中,然后按一下回车保存,之后会提示是否重启,然后按一下回车就可以重启了。

重启完成后使用

ls /dev/ ttyS*

来查看可操作的设备文件,这里只会显示你开启的设备

img

我们刚才选择了UART7后它这里就会有相应的设备,那个ttyS1是默认开启的,我们不用理他。

2.接线

这里就需要非常非常注意了,我在测试的时候就是这里没有弄好,线接反了,导致一直发送不了。

按照前面给的那张图进行接线后我们就可以写代码了。

注意:这里很重要,很多人玩51单片机来玩这个会忽略一个问题就是GND必须要和单片机的GND接在一起,很多人会单独给这个引脚接GND是错误的。

3.串口函数

这里使用的串口函数库是

#include <wiringSerial.h>

3.1 打开串口文件

int serialOpen(const char* path, int butrl);

第一个参数是设备的路径,比如说我们这需要使用到UART1,那设备地址就是/dev/ttyS7

第二个参数是波特率,比如9600。

这个函数的返回值是一个文件描述符,我们使用一个变量来接收就可以了。

3.2 关闭串口文件

void serialClose(int fd);

参数是打开的设备描述符。

3.3 发送一个字符

void serialPutchar(int fd, char c);

将一个字符发送到打开的文件描述符fd所对应的设备文件。

3.4 发送一个规定好的字符串

void serialPuts(int fd, char* s);

发送一个规定好的字符串,然后以0结尾。

3.5 printf

void serialPrintf(int fd, char* message, ...);

这个和printf的用法类似。

3.6 返回等待读取的字符数

int serialDataAvail(int fd);

返回的是等待读取的字符数。

3.7 读取字符

int serialGetChar(int fd);

返回下一个待读取的字符,如果这个在10s内没有读取到字符就会返回-1。

3.8 缓冲区函数

void serialFlush(int fd);

抛弃所有接收的数据或者等待写入设备完成。

4.输出内容

通过学习了上面的内容后我们可以简单写一个输出字符的代码来输出0~9

#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>

int main(){
    int fd = -1, i;
    wiringPiSetup();
    fd = serialOpen("/dev/ttyS7", 9600);    // 设置串口设备和波特率
    if (fd < 0){
        // 设备打开失败
        printf("设备打开失败\n");
        return -1;
    }
    serialFlush(fd);
    for (i = 0; i < 10; i++){
        serialPutchar(fd, i + '0');
    }
    
    serialClose(fd);     // 关闭设备
}

运行效果如下:

img

然后也可以使用printf来个高级一点的输出

#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>

int main(){
    int fd = -1, i;
    wiringPiSetup();
    fd = serialOpen("/dev/ttyS7", 9600);    // 设置串口设备和波特率
    if (fd < 0){
        // 设备打开失败
        printf("设备打开失败\n");
        return -1;
    }
    serialFlush(fd);
    for (i = 0; i < 10; i++){
        serialPrintf(fd, "this is%d\r\n", i);
    }
    
    serialClose(fd);     // 关闭设备
}

输出的内容如下:

img

5.接收内容

我们可以通过串口来接收一下用户输入的内容,并判断输入的字符是不是我们需要的,然后通过串口返回

#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>

int main(){
    int fd = -1, i;
    int c;
    wiringPiSetup();
    fd = serialOpen("/dev/ttyS7", 9600);    // 设置串口设备和波特率
    if (fd < 0){
        // 设备打开失败
        printf("设备打开失败\n");
        return -1;
    }
    serialFlush(fd);
    while(1){
        c = serialGetchar(fd);
        if (c == -1){
            break;
        }
        if (c == '1'){
            serialPrintf(fd, "this is 1");
        }
        printf("%c", c);
    }
    
    serialClose(fd);     // 关闭设备
}

输出的内容:

img

这里大家可以看到我在这个while循环中添加了一个printf,但是没有截屏给出效果,是因为在这个串口运行的过程中一直在读取的是串口的设备,没有使用输入输出流设备,所以导致我们在输入后没办法显示出内容来,这个的解决办法我还不太清楚,等我研究好了我在出一篇文章来介绍。

6.接收字符串

做串口最让人沉醉的就是读取字符串的操作了,在wiringSerial中有一个函数可以获取缓冲区的长度,我们可以判断这个来读取输入的数据的长度,然后使用一个for循环来循环读取和放置在一个数组中,之后进行输出即可,代码如下:

#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>

int main(){
    int fd = -1;
    int i, ch, j = 0, len;
    char str[1024];
    wiringPiSetup();
    fd = serialOpen("/dev/ttyS7", 9600);
    if (fd < 0){
        printf("打开设备文件失败\n");
        return -1;
    }
    while(1){
        len = serialDataAvail(fd);     // 获取缓冲区的长度
        if (len == -1){
            // 超时退出循环
            break;
        }
        else if(len > 0){
            serialPrintf(fd, "%d\n", len);    // 这一行是测试的
            for (i = 0; i < len;  i++){
                ch = serialGetchar(fd);       // 读取字符
                str[j++] = ch;
            }
            break;
        }
        
    }
    
    str[j] = '\0';
    printf("%s\n", str);
    serialClose(fd);
    return 0;
}

三、使用hc05连接香橙派

学会了上面的操作后我们就可以来用香橙派连接hc05了,这个操作也非常非常的简单,和单片机的连接一致,注意不要连错就行了。

img

1.通过蓝牙发送字符串给手机

本质的思路还是一样的,就是使用串口是输入输出就可以搞定,代码如下:

#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>

int main(){
    int fd = -1, i;
    int c;
    wiringPiSetup();
    fd = serialOpen("/dev/ttyS7", 9600);    // 设置串口设备和波特率
    if (fd < 0){
        // 设备打开失败
        printf("设备打开失败\n");
        return -1;
    }
    serialFlush(fd);
    while(1){
        serialPrintf(fd, "hello\r\n");
        delayMicroseconds(30000);     // 延时函数,免得发送得过快
    }
    
    serialClose(fd);     // 关闭设备
}

然后在调试助手中就可以看到蓝牙模块发送的内容了:

img

2.手机发送内容给香橙派

这里可以使用刚才我们测试的接收字符串,只不过这里有一个问题就是这个工具只能发送字符所对应的ascii码,不能正常的输入字符进行输出。

比如我们手机输入字符0,那就得输入ascii码的30

img

然后在香橙派中就会显示:

img

然后我们还可以用这个来输入0~9

img

然后显示

img

学会了这个后我们就可以编写一个用蓝牙点灯的程序了,其实就是使用蓝牙接收到关键字后给LED灯高低电平,这里就直接给代码了,不展示图片了

#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <string.h>

int main(){
    int fd = -1;
    int i, ch, j = 0, len;
    char str[1024];
    wiringPiSetup();
    fd = serialOpen("/dev/ttyS7", 9600);
    if (fd < 0){
        printf("打开设备文件失败\n");
        return -1;
    }
    while(1){
        len = serialDataAvail(fd);
        if (len == -1){
            break;
        }
        else if(len > 0){
            serialPrintf(fd, "%d\n", len);
            for (i = 0; i < len;  i++){
                ch = serialGetchar(fd);
                str[j++] = ch;
            }
            break;
        }
        
    }
    
    str[j] = '\0';
    printf("%s\n", str);

    if (strcmp(str, "0")){
        // 点灯
    }
    else{
        // 灭灯
    }

    serialClose(fd);
    return 0;
}

总结

串口很好玩,蓝牙模块也好玩,学会了这一节我们就可以使用蓝牙做一个简单的远程开关灯的项目了,当然拿香橙派做这个项目就很浪费,毕竟香橙派可以直接使用网络来实现,做一个网页,然后内网穿透,这样可以很容易的在远程进行控制了,等我最近忙完有时间我做一个。

大家可以关注一下我的论坛和博客,有不懂的或者有趣的问题可以放在我的论坛中进行提问

马桶论坛

马桶博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/365105.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【教学类-44-04】20240130 print dashed(虚线字体)制作的数字描字帖

作品展示&#xff1a;背景需求&#xff1a; 制作绿色数字的数字描字帖 选用字体&#xff1a;print dashed&#xff08;虚线字体&#xff09; 【教学类-44-03】20240111阿拉伯数字字帖的字体&#xff08;三&#xff09;——德彪钢笔行书&#xff08;实线字体&#xff09;和pri…

vue3前端开发,element-plus前端框架探秘:scope对象

vue3前端开发&#xff0c;element-plus前端框架探秘:scope对象&#xff01;我们经常需要对当前行的数据进行操作&#xff0c;比如增加&#xff0c;删除&#xff0c;编辑等&#xff0c;为此我们需要传递当前行所对应的唯一主键,通常情况下&#xff0c;当前行对应的业务主键是id属…

DHCP简介

定义 动态主机配置协议DHCP&#xff08;Dynamic Host Configuration Protocol&#xff09;是一种用于集中对用户IP地址进行动态管理和配置的技术。即使规模较小的网络&#xff0c;通过DHCP也可以使后续增加网络设备变得简单快捷。 DHCP是在BOOTP&#xff08;BOOTstrap Protoc…

前端使用cache storage实现远程图片缓存

Cache Storage 的主要特点和用途 缓存网络资源&#xff1a;可以将经常访问的网络资源缓存到 Cache Storage 中&#xff0c;以提高网页加载速度&#xff0c;减少网络请求。离线访问&#xff1a;当用户处于离线状态时&#xff0c;可以使用 Cache Storage 中的缓存资源来加载网页…

STM32目录结构

之前一直头疼的32目录&#xff0c;比51复杂&#xff0c;又没有C规律&#xff0c;也不像python脚本文件关联不强&#xff0c;也不像工整的FPGA工程&#xff0c;编的时候到处放&#xff0c;爆出的错千奇百怪。短暂整理了一个&#xff0c;还是没有理得很轻。 startup_stm32f10x_m…

这两年,技术圈最流行什么前端框架?

2023 年&#xff0c;TypeScript 的每月下载量持续稳定增长&#xff0c;年度累计下载量高达2,071,832,110&#xff08;20.7 亿&#xff09;&#xff0c;展现了强大的市场需求和用户认可。 本文来通过详细的数据&#xff08;2023 年 npm 累计下载量&#xff09;&#xff0c;看看…

【论文阅读笔记】Advances in 3D Generation: A Survey

Advances in 3D Generation: A Survey 挖个坑&#xff0c;近期填完摘要 time&#xff1a;2024年1月31日 paper&#xff1a;arxiv 机构&#xff1a;腾讯 挖个坑&#xff0c;近期填完 摘要 生成 3D 模型位于计算机图形学的核心&#xff0c;一直是几十年研究的重点。随着高级神经…

视频融合平台EasyCVR推流成功但平台显示不在线是什么原因?

TSINGSEE青犀视频监控汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&…

Excel——自定义排序、多条件排序

一、自定义排序 Q&#xff1a;请按照“新疆、湖北、天津、北京、湖南”的先后顺序整理以下表格 A&#xff1a;步骤如下&#xff1a; 选择表格中任意一个单元格 选择【排序】——【自定义排序】 选择主要关键字【地区】&#xff0c;在次序中选择【自定义序列】 选择【添加】&…

第8章 磁盘管理

第8章 磁盘管理 8.1 标准磁盘管理 8.1.1 两种分区格式 MBR&#xff08;Master Boot Record&#xff0c;主引导分区&#xff09;&#xff0c;支持 4 个主分区或者 3 个主分区 1 个扩展分区&#xff0c;分区的空间最大支持 2.2 TB。 GPT&#xff08;GUID Partition Table&…

2024美赛C题完整解题教程 网球运动势头(持续更新)

2024美赛已经于今天早上6点准时公布题目。本次美赛将全程跟大家一起战斗冲刺O奖&#xff01;思路持续更新。 2024 MCM Problem C: Momentum in Tennis &#xff08;网球运动的势头&#xff09; 注&#xff1a;在网球运动中&#xff0c;"势头"通常指的是比赛中因一系…

LLM App SDK:LangChain vs. LlamaIndex

在Why RAG is big中&#xff0c;我表示支持检索增强生成&#xff08;RAG&#xff09;作为私有、离线、去中心化 LLM 应用程序的关键技术。 当你建造一些东西供自己使用时&#xff0c;你就是在孤军奋战。 你可以从头开始构建&#xff0c;但在现有框架上构建会更有效。 NSDT工具推…

如何做好员工离职风险防范和离职危机处理工作

员工退出与离职是企业发展中都会面临的一个普遍现象&#xff0c;这种现象本身没有什么问题&#xff0c;但是如果企业退出与离职管理不善&#xff0c;就会增加企业的管理成本&#xff0c;影响企业的正常经营活动。该电子科技有限公司在发展中也遇到员工离职管理不善带来的问题。…

mfc140.dll丢失的几种修复方式,有效的解决文件丢失问题

mfc140.dll是Microsoft Foundation Class (MFC)库中的一个非常重要的DLL文件。它承载了许多被执行程序使用的函数和资源。这个库主要被广泛应用于开发Windows操作系统上的应用程序。然而&#xff0c;有时候我们可能会遭遇到mfc140.dll缺失或损坏的情况&#xff0c;这会导致依赖…

MongoDB聚合操作

文章目录 聚合操作单一作用聚合聚合管道什么是 MongoDB 聚合框架管道&#xff08;Pipeline&#xff09;和阶段&#xff08;Stage&#xff09;常用的管道聚合阶段聚合表达式数据准备$project$match$count$group accumulator操作符$unwind$limit$skip$sort$lookup案例聚合操作案例…

Unity 协程(Coroutine)高级用法实例

文章目录 简介1. 异步加载场景并显示加载进度2. 延迟执行与定时器功能3. 动画序列控制4. 网络请求处理5. 渐变颜色或透明度变化6. 游戏对象的动态移动序列7. 处理帧率依赖的时间间隔8. 异步加载资源并在完成后初始化 简介 在Unity中&#xff0c;C#协程&#xff08;Coroutine&a…

Qt QCustomPlot 鼠标悬浮提示

使用QCustomPlot绘图时&#xff0c;相信大多数童鞋们都会有类似的诉求&#xff1a;希望鼠标移动到节点时&#xff0c;可以显示该节点的数据。这里转载了一篇关于 鼠标悬浮提示 的一篇文章&#xff0c;并对该文章涉及的代码经过了整理&#xff0c;经实践证明是可行的。 鼠标悬浮…

操作系统--Linux内核进程间的通信方式

每个进程的用户地址空间都是独立的&#xff0c;一般而言是不能互相访问的&#xff0c;但内核空间是每个进程都共享的&#xff0c;所以进程之间要通信必须通过内核。 一、管道 匿名管道&#xff1a;它没有名字标识&#xff0c;匿名管道是特殊文件只存在于内存&#xff0c;没有存…

二、Gradle 与 Idea 整合

这里写自定义目录标题 1、Groovy简介2、Groovy 安装3、创建 Groovy 项目4、Groovy 基本语法5、在 idea 中创建普通 java 工程 1、Groovy简介 详细了解请参考&#xff1a;http://www.groovy-lang.org/documentation.html 2、Groovy 安装 下载后解压到本地 验证&#xff1a; …

springboot完成一个线上图片存放地址+实现前后端上传图片+回显

1.路径 注意路径 2.代码&#xff1a;&#xff08;那个imagePath没什么用&#xff0c;懒的删了&#xff09;&#xff0c;注意你的本地文件夹要有图片&#xff0c;才可以在线上地址中打开查看 package com.xxx.common.config;import org.springframework.beans.factory.annotat…
最新文章