简介:面向工业机器人二次开发与调试人员的汇川IRCB-501控制API演示资源,以软件/插件形式提供可参考的Demo工程与调用示例,适合需要快速了解汇川机器人通信接口、控制指令封装及基础联调流程的开发者,也适合具备PLC或运动控制基础、希望评估机器人二次开发方案的自动化集成商。压缩包共1248个文件,约92.83MB,核心内容包括dll动态库与xml配置/数据文件,支撑API调用与参数读取;cs/csproj/sln等源代码工程便于查阅示例逻辑;txt/md文档用于说明使用方式,png/jpg截图辅助界面核对,其余nupkg、pdb、cache等为构建与调试附属文件,整体目录结构完整,可覆盖从环境还原到运行验证的主要环节。目前已有329人学习下载。借助该demo可快速建立IRCB-501控制项目骨架,梳理API入参、返回值与异常处理思路,减少从零阅读手册的耗时,便于在此基础上做工艺动作定制与二次开发。
1. 汇川机器人 API 控制 IRCB-501 demo:从示教器到代码控制
现场做过机器人调试的人都有体会:产品换型时,示教器上几十个点位要一个个手动改,改完还要回原点跑一遍,半天就耗在重复劳动上。汇川机器人 API 控制 IRCB-501 这个方向,就是用代码把示教器上那套操作搬到 PC 上——连接控制器、读点位、下发运动指令、监控状态,全部写成脚本批量跑。这个 demo 谈不上多复杂,但它解决的是产线上最实际的效率问题。第一次在模拟项目X里用 API 批量下发点位,几十个坐标十几秒就写完,速度快到有点不真实。适合刚接触机器人二次开发的工程师,也适合想把点位管理纳入产线系统的自动化团队。
2. IRCB-501 的控制通道与 API 协议:先搞懂控制器给了什么
2.1 控制器的三种接入方式:示教器、数字 IO 与网络 API
IRCB-501 这个控制器,常规用法是配示教器,现场所有点位的示教、速度调整、报警复位都在示教器上完成。示教器的优势是直观,几乎没有学习成本,新来的操作工半天就能上手。但示教器操作模式限制了批量效率:所有点位要人工一份份设置,点位之间的逻辑要靠编写示教程序实现,而且这种方式无法和上位的 MES、视觉系统做数据交互。订单一变,点位数据就要重新导一遍,产线换型时间被拉得很长。
数字 IO 是最简单的自动化接口,PLC 通过硬接线给控制器发启动、暂停、复位信号。它的优点是实时性和确定性都好,电气工程师和电工都熟悉这套方案,抗干扰能力也强。但缺点是只能传开关量,点位坐标、速度参数这类连续量传不了,机器人只能执行预先写好的固定轨迹。如果产品只有两三种型号,用 IO 切换程序号完全够用;一旦型号多起来,点位数量和切换逻辑会变得难以维护。
网络 API 是三者里信息量最大的通道。IRCB-501 控制器上带有以太网口,PC 通过 TCP/IP 与控制器内的后台服务通信,既能读位姿、IO 状态、报警码,也能下发绝对位置运动、速度切换、程序启停等指令。这个 demo 的核心就是走这条通道。网络 API 还有一个示教器比不了的优势:可远程调试。工程师不用站在电柜前面就能操作机器人,抓包、回放、自动化测试都能做,排错效率高一个数量级。
从选型角度给个实际经验:如果需求是“按钮一按跑完固定轨迹”,数字 IO 是最可靠、成本最低的方案,别为了上 API 而上 API。如果需求是“订单变化时动态改点位、速度”,网络 API 是唯一能落地的方式。现实项目里两种方式常常混用——PLC 负责安全回路和节拍,API 负责点位数据和参数下发。这个 demo 讲的是网络 API,但落地时别忘了 IO 回路依然是安全底线。
2.2 TCP API 的帧格式与交互状态机
接触一套新控制器的 API,第一件事是看它的通信规约。不同品牌、不同固件版本的报文格式会有差异,但工业控制器的 API 大多遵循相近的套路:帧头 + 长度 + 指令码 + 数据 + 校验。下面是二次开发里比较通用的一种帧格式,我按这个约定实现了 demo 的代码。
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 1 字节 | 0xAA 表示请求帧,0xBB 表示响应帧 |
| 数据长度 | 2 字节 | 从指令码开始到数据区结束的总字节数,大端序 |
| 指令码 | 2 字节 | 0x0001 登录建连,0x0002 心跳,0x0010 绝对运动,0x0011 查询关节角,0x0012 查询笛卡尔位姿 |
| 数据区 | 变长 | 参数按指令码定义顺序排列 |
| CRC16 | 2 字节 | Modbus CRC16,覆盖从帧头到数据区结束 |
为什么用 Modbus CRC16 而不是更简单的和校验?因为工业现场电磁干扰比办公室大得多,和校验容易漏检,CRC16 在短帧场景下误检率足够低。实现时注意 CRC 计算要逐字节按位运算,网上抄来的查表法容易出现参数表高低位不一致的问题,看着算对了,实际校验全错,就是一个暗坑。
交互状态机可以从两个角度看。从 TCP 角度看,PC 是客户端,IRCB-501 控制器是服务端,监听一个固定端口,连接建立后双方保持长连接。从业务角度看,双方有一个简化的会话状态:未登录、已登录、就绪、运动中。控制器在未登录状态只接受查询指令,不接受运动控制指令和参数修改指令。这样的设计是为了安全,避免任意一个能连上控制器的设备随意开动机器人。
demo 里我用“建连报文同时完成登录”的做法:TCP 建立后,第一条报文就是登录指令,数据区携带 token。这样状态机直接从“未登录”跳到“就绪”,后续代码不用单独处理登录流程。控制器若校验失败,一般会返回一个带错误码的响应帧,但不会主动断开 TCP 连接。这个行为很像黑匣子,你看不到错在哪,只能靠抓包和文档确认编码方式。也有少数固件版本在连续登录失败后会临时封禁客户端 IP,遇到这种情况先停下来排查,不要暴力重试。
2.3 控制器的坐标系与单位约定
写 API 控制代码之前,坐标系和单位这两件事一定要先搞清楚,否则后面全是玄学问题。IRCB-501 这类控制器通常支持基坐标系、法兰坐标系和工具坐标系。基坐标系是机器人底座原点,适合描述整个工位里工件的绝对位置;法兰坐标系在机器人末端,适合描述工具安装角度;工具坐标系则是在法兰坐标系基础上加上了工具长度偏移,适合直接控制焊枪、吸盘这类末端工具。API 查询和运动指令一般会带一个坐标系选择字段,默认值通常是工具坐标系,而示教器界面默认显示的是基坐标系。这个不一致,是位姿数值和示教器对不上的主要原因之一。
单位约定上,位置量的常见单位是毫米,角度量可能是度也可能是弧度,速度更是有两种惯用法:百分比(相对额定速度)和线性速度(mm/s)。在第四章代码里我会用百分比作为默认。具体到现场,以控制器侧的配置界面为准,不要想当然。我在一个模拟项目X里就吃过亏,把 50mm/s 当成了 50% 下发,机器人慢得像蜗牛,排查了半天才发现是单位理解错了。单位问题在协议文档里往往只占一行,但影响是数量级的,值得在写代码前确认清楚。
3. 搭一个能跑的 API 控制环境:网络配置与最小 demo
3.1 连接前的网络配置:IP、端口和防火墙
IRCB-501 控制的机器人进到现场,第一步不是写代码,而是把物理链路弄明白。控制器上一般有以太网口,有的型号还保留着示教器直连回路。我的习惯是先把 PC 的网线直连控制器网口,把 PC 的 IP 手动设成和控制器的同一网段。假设控制器 IP 是 192.168.1.10,PC 就设 192.168.1.100、掩码 255.255.255.0,先确保能 ping 通再往下走。ping 不通的时候,先检查网线是不是交叉线、控制器网口是否启用,这两个问题占了一半。
控制器监听的 API 端口在老版本里可能是 9000,新一点的是 10001,也有一些实施现场把端口改成 20000 避免冲突。拿不准的时候,用 TCP 端口扫描工具扫一下控制器 IP,看哪些端口在监听。这里有个提醒:端口扫描在生产网段里会被安全设备当作攻击行为,尽量选在停机调试的时间做,扫完就关。如果控制器有多个网口,还要确认哪个网口绑定了 API 服务,别扫出来一堆端口却连错了物理口。
防火墙方面,Windows 默认会拦截从 Python 发起的 TCP 出站连接吗?大多数情况下不会,但公司安全策略会往系统里装主机防火墙软件,效果就说不准了。demo 阶段如果连接一直超时,先把 Windows 防火墙和第三方安全软件临时关掉,确认是防火墙问题后,再添加一条针对控制器 IP 的放行规则。不建议用关闭防火墙的方式长期运行,这不是安全做法。
网络通了之后,建议先做一次链路验证:用 Python socket 连一次端口,看控制器是否会主动发握手包,或者对任意字节返回错误响应。这一步能确认端口没错、服务在监听,把“网络问题”和“协议问题”切开。很多人在这一步省掉了,结果后面排查了半天才发现是 IP 配错,浪费时间。
3.2 最小连接代码:建连、登录与心跳
demo 的开发语言我选了 Python,没有别的高深理由:开发速度快,字符串和字节处理方便,后期哪怕要交给维护人员也好改。我先实现组帧、建连和登录:
import socket import struct import time import threading ROBOT_IP = "192.168.1.10" # IRCB-501 控制器 IP,按现场实际修改 ROBOT_PORT = 10001 # API 服务端口,按示教器配置修改 AUTH_TOKEN = "YOUR_TOKEN" # 登录 token,由控制器管理员生成 def crc16_modbus(data: bytes) -> int: crc = 0xFFFF for b in data: crc ^= b for _ in range(8): if crc & 1: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc def build_frame(cmd: int, data: bytes = b"") -> bytes: body = struct.pack(">H", cmd) + data frame = b"\xaa" + struct.pack(">H", len(body)) + body crc = crc16_modbus(frame) return frame + struct.pack(">H", crc) sock = socket.create_connection((ROBOT_IP, ROBOT_PORT), timeout=5) login_payload = AUTH_TOKEN.encode("utf-8") + b"\x00pc_demo_01" sock.sendall(build_frame(0x0001, login_payload)) resp = sock.recv(1024) print("login response hex:", resp.hex())逻辑说明:crc16_modbus 逐字节计算,逻辑透明、不依赖查表;build_frame 把指令码和数据拼成 body,再在前面加帧头和长度字段,最后追加 CRC;登录指令 0x0001 的数据区由 token、分隔符、客户端名组成。响应帧里能看到 0xBB 帧头,状态码字段为 0 代表登录成功。
参数说明:ROBOT_IP 和 ROBOT_PORT 是唯一必须改的两个配置,现场以实测为准。AUTH_TOKEN 如果控制器未启用登录校验,传空字符串也能过,但于安全考虑不建议这么做。timeout 设在 5 秒,局域网内够用;如果控制器负载高,把 timeout 增大到 10 秒也可以,但不要用无限超时,否则程序会永久卡死在 recv。
登录完成之后,建议立刻做两件事:第一,发一条查询指令确认控制器确实接受你;第二,启动心跳线程,避免控制器因为空闲把连接断开。心跳我一般每 2 秒发一次:
def heartbeat_loop(sock: socket.socket, stop_event: threading.Event): # 每 2 秒发一次心跳指令,保持会话活性 while not stop_event.is_set(): try: sock.sendall(build_frame(0x0002, b"")) except (BrokenPipeError, ConnectionResetError): break except Exception: break stop_event.wait(2) stop_evt = threading.Event() th = threading.Thread(target=heartbeat_loop, args=(sock, stop_evt), daemon=True) th.start()这里要特别注意 TCP 的流式传输特性。sock.recv(1024) 接收到的不一定是一整帧数据,可能是一帧的一部分,也可能是两帧拼在一起。demo 代码里为了直观而直接 recv,但这个写法在生产环境是隐患。第四章会给一个带拆帧的 recv_frame,正式代码请使用那个版本。
3.3 验证连接是否成功的三个快速方法
连接成功后,我先用三种方法确认链路和协议都没问题,再开始运动控制测试。
第一种,回环查询:连续调用查询状态指令,看返回的状态码是否稳定为 0、运动标志是否与示教器显示的机器人状态一致。如果运动标志一直是 0,说明控制器认为机器人空闲,可以继续下一步。
第二种,示教器对照:示教器屏幕上手动移动机器人到某个位置,然后用 API 查询位姿,对比两者的数值差异。如果坐标系和单位没搞清楚,这一步就能暴露问题。坐标系不一致在示教器对照的测试里表现为固定差值,而不是随机误差,很容易识别。
第三种,断线恢复:把 PC 网线拔掉再插回去,看控制器是否主动断开连接、代码能否重连。这决定了后续异常处理要怎么设计。控制器对断线的处理各有不同,有的立即断开会话,有的要等 TCP 超时,实测结果直接决定心跳周期的设置。我见过一个项目里控制器断线 30 秒后才释放会话,而重连代码一直在反复失败,最后只能加长重连间隔。
4. 实现点位移动与状态读取:核心控制功能
4.1 查询当前位姿与控制器状态
运动控制的前提是知道机器人此刻在哪儿。IRCB-501 的 API 一般把“查询关节角”和“查询笛卡尔位姿”拆成两个指令,关节角适合做轨迹规划和干涉区判断,笛卡尔位姿适合人读。我以笛卡尔位姿为例。正式代码里,收帧不能像 demo 那样直接 recv,必须做拆帧。下面这个 recv_frame 会把一帧完整收齐:
def recv_frame(sock: socket.socket) -> bytes: header = b"" while len(header) < 3: chunk = sock.recv(3 - len(header)) if not chunk: raise ConnectionError("connection closed while reading header") header += chunk data_len = struct.unpack(">H", header[1:3])[0] body = b"" while len(body) < data_len: chunk = sock.recv(data_len - len(body)) if not chunk: raise ConnectionError("connection closed while reading body") body += chunk return header + body def query_pose(sock: socket.socket): sock.sendall(build_frame(0x0012, b"")) frame = recv_frame(sock) # 帧: 帧头1 + 长度2 + 指令码2 + 状态码2 + 6个float位姿 status = struct.unpack(">H", frame[5:7])[0] x, y, z, a, b, c = struct.unpack(">6f", frame[7:31]) return status, (x, y, z, a, b, c)逻辑说明:recv_frame 先读满 3 字节帧头,解出数据长度,然后循环读满数据体。如果连接中途断开会抛异常,调用方就能感知到网络问题,而不是永久阻塞。query_pose 返回的元组是 X、Y、Z、A、B、C,角度单位以控制器设置为准,多数场合是度。
参数和边界说明:数据长度字段的大端序和小端序必须和 build_frame 保持一致。如果你的控制器固件用小端序,这里要把>H全部换成<H,float 的部分也要同步换。还有一种常见情况:控制器在数据区尾部附加了时间戳等附加字段,导致 body 比预期长,解析时要跳过附加字段而不能直接解包到帧尾。遇到这种情况,找文档里的附加字段定义,没有文档就多发几帧对比差异。
4.2 发送绝对位置运动指令
绝对位置运动是这个 demo 的核心动作:指定目标笛卡尔坐标,控制器规划轨迹并执行。指令码我用 0x0010,数据区按顺序放 6 个位姿 float 加上速度和加速度 float。
def move_abs(sock: socket.socket, x: float, y: float, z: float, a: float = 0.0, b: float = 0.0, c: float = 0.0, speed: float = 50.0, accel: float = 50.0): # 8 个 float:位姿 6 个 + 速度 + 加速度 payload = struct.pack(">8f", x, y, z, a, b, c, speed, accel) sock.sendall(build_frame(0x0010, payload)) frame = recv_frame(sock) status = struct.unpack(">H", frame[5:7])[0] return status调用这个函数时,注意三点。
第一,这是异步执行模型。move_abs 返回的状态码只表示控制器接受了指令,不表示机器人已经到位。demo 阶段用一个简单的到位轮询:在循环里调 query_pose,计算当前位置与目标的欧氏距离,小于阈值(比如 0.5mm)就认为到位。这个阈值不能太小,不然机器人到位抖动会让程序永远等不到位;也不能太大,否则下一段运动的起点偏移会累积。0.5mm 对多数装配场景够用。
第二,运动指令的坐标系。move_abs 的 6 个参数是笛卡尔坐标,但代码里没有传坐标系选择字段。如果控制器的 0x0010 指令内置了默认工具坐标系,而你传入的是基坐标系坐标,机器人会走到一个完全不对的位置。我建议在登录后调用“设置坐标系”指令,把工具号和工件号都显式设置好。设置方法在不同固件里差异很大,以现场 API 文档为准,但原理都一样:工具号、工件号、坐标系选择字段三者要一致。
第三,连续运动时的时序。发完 move_abs 后立即发下一条,大概率会碰到“运动冲突”错误。正确的节奏是:发第一条、轮询到位、发第二条。如果要做得更顺滑,可以用第五章的方法做预告,但 demo 阶段先把时序做对。这也是新人在现场最容易卡住的地方,记住一个原则:接受指令不等于执行完毕。
4.3 速度切换与 IO 控制:让 demo 更像真实工位
真实的工位不会只有连续运动,还要控制夹具、视觉触发等外围设备。IRCB-501 的控制器一般也带数字 IO,API 可以读写这些 IO 点。一个典型的夹取动作是:移动到取料点,输出 IO 让夹具闭合,等待夹具到位信号,再移动。
def set_do(sock: socket.socket, io_index: int, value: bool): # 数据区:IO索引(2字节) + 值(1字节) + 保留字节 payload = struct.pack(">H?B", io_index, value, 0) sock.sendall(build_frame(0x0020, payload)) frame = recv_frame(sock) return struct.unpack(">H", frame[5:7])[0] def get_di(sock: socket.socket, io_index: int) -> bool: payload = struct.pack(">H", io_index) sock.sendall(build_frame(0x0021, payload)) frame = recv_frame(sock) # 数据区: 状态码 + IO值(1字节) return bool(struct.unpack(">B", frame[7:8])[0])IO 字节序和地址编号跟控制器的 IO 映射表强相关。set_do 的第三个参数我加了一个保留字节 0,这样做是为了保持数据区长度对齐,避免协议解析器按长度判断出问题。写 IO 之前,先从示教器的 IO 监视页面确认 IO 编号,不要按端子排上的物理编号想当然——硬件编号和软件映射之间经常有错位。
如果控制器支持通过 API 快速切换速度,我会把 move_abs 和速度切换配合使用:接近工件时用 30% 速度,确保定位准确,离开时切到 80% 速度,提高节拍。速度切换指令同样要在两段运动之间发出,才会对下一段运动生效。正在执行的运动段不会中途变速,这是控制器的普遍行为,别指望它能像汽车油门一样随时响应。
5. IRCB-501 API 控制避坑:五个高频问题复盘
这一章把我在不同现场遇到的典型问题做一个复盘,每一条都按“现象 → 原因 → 解决”来写。这些问题没有一个属于难解的原理问题,全部是实施方案的细节坑,但每一个都足以让 demo 卡住半天。
5.1 连接成功但指令全部无响应
现象:TCP 能连上,登录也返回了正常帧,但后续所有指令都收不到响应,像是连接被挂起了。
原因:最常见的是登录 token 校验不通过但控制器没有断开连接。控制器进入只读状态,查询指令可能还有响应,运动指令、参数修改指令一律沉默。另一个隐蔽原因是登录成功后没有发心跳,控制器启动了空闲超时,后续报文被丢弃。
解决:第一步,抓包看控制器是否返回错误响应帧,错误码就是线索。第二步,核对 token 是否与示教器上当前有效 token 一致——某些固件里 token 会随系统时间变化,系统时间被重置后 token 失效,需要重新生成。第三步,确保心跳线程在登录后立即启动,心跳周期不要大于控制器配置的空闲超时。我在某个模拟项目X里就遇到过,心跳周期设成 30 秒而控制器超时是 10 秒,程序每跑 20 分钟就“断线”一次。
5.2 连续下发多条指令,机器人动作不连贯
现象:明明连续发了两条运动指令,机器人却像是每段都停下来重新启动,节拍慢了一大截。
原因:控制器默认单段执行模式,一条运动指令完成后才会接纳下一条。如果下一条指令到达时上一段还在执行,控制器要么拒绝,要么把它当作新的独立运动段,两种情况下段与段之间都会有减速、停止、再加速的过程。
解决:查看控制器是否支持连续轨迹模式或轨迹预规划队列。如果支持,在登录后设置运动模式为连续,段间过渡会变成圆弧过渡或平滑过渡。如果不支持,就在代码层做预告:当当前位置与目标点的距离小于一个安全余量(比如 10mm)时,不等完全到位就把下一条运动指令发出去。这个余量要根据机器人速度和减速能力调整,太大了轨迹会拐弯,太小了跟没预告一样。
5.3 报警后 API 不接受任何运动指令
现象:急停按钮被拍下去、或者轴过载报警发生后,API 查询正常,但运动指令一律返回错误码。
原因:这是控制器固件的安全锁逻辑,不是 bug。报警状态下运动输出被硬件和软件双重锁定,API 层不可能绕过。就算你重发一百遍运动指令,控制器也不会让机器人动,这是安全设计,必须尊重。
解决:程序读到错误码后,先调用查询报警码的指令,确认是哪一类报警。如果 API 支持报警复位指令,复位后再把机器人手动或自动移回安全位置。注意,报警复位不等于急停恢复,很多现场要求操作人员确认安全后手动复位急停按钮,程序里不能把这个确认过程自动化跳过。
5.4 API 位姿数值和示教器显示对不上
现象:机器人停在同一个位置,API 读出的 X/Y/Z 和示教器界面差了几毫米甚至更多,怀疑数据链路有问题。
原因:坐标系不一致几乎是唯一原因。示教器界面通常默认基坐标系,API 指令可能默认工具坐标系,两者之间隔着工具长度偏移和安装角度。也有少数情况是单位问题,比如角度单位混用了度和弧度。
解决:先做对照测试,手动把机器人移动到示教器可读的位置,记录坐标,再用 API 查询。如果差值固定,就是坐标系偏移,可以在代码里减去偏移量。更好的做法是设置控制器侧的当前坐标系为基坐标系,再发查询指令。设置坐标系的方法在不同固件里差异较大,以 API 文档为准,但原理是一样的:工具号、工件号、坐标系选择字段三者要一致。
5.5 程序崩溃后机器人还在继续跑
现象:PC 端 Python 进程异常退出,控制器没有让机器人停下来,最后一条运动指令执行完毕。
原因:控制器的安全策略只保证“断开连接后不再接受新指令”,不会主动中止正在执行的运动段。这个设计本身是合理的——很多场合机器人需要在断线后走完当前动作才能停止在安全位置,强行急停反而会造成意外。
解决:产品级实现要在控制器侧开启“通信超时自动停止”功能,并用心跳机制维持会话。如果 API 文档里有看门狗周期参数,设成一个保守值,比如 500ms。demo 阶段至少保证代码用 try/finally 包裹运动控制流程,finally 里发急停指令并关闭 socket。千万别图省事直接 kill 进程,让控制器进入无人监管的运行状态。
6. 进阶:把 API 控制 demo 升级成可靠的上位控制模块
6.1 用状态机组织控制逻辑
点位少的时候,线性脚本足够;点位一多、交互一变多,线性代码就是一场灾难。我的习惯是把每个工位动作拆成一个状态机:IDLE、CHECK_READY、MOVING、WAIT_IO、FINISH、ERROR。事件来源是控制器响应、IO 输入和定时器。状态机的好处是每一步都是确定的,日志里能完整重现流程。
“取料动作”的状态序列:IDLE 收到启动 → CHECK_READY 确认原点和无报警 → MOVING 发运动指令 → 到位后输出夹具闭合 IO → WAIT_IO 等夹具到位信号 → 发运动指令去放料点 → FINISH。任一步返回错误码就转入 ERROR,记录错误码和位姿,方便后续恢复。
6.2 日志与上线检查清单
demo 阶段功能跑通,上线阶段能定位问题。我给控制模块写了一个 logger,每发出和收到一帧报文都记录带时间戳的十六进制日志。排查偶发问题时,把日志和抓包工具结果对照,能快速定位是链路、组帧还是控制器逻辑问题。
上线前我一条条核对:心跳超时自动急停已开启;PC 程序断线重连后能恢复状态;所有 socket 调用都有超时;运动指令发送前校验状态和报警码;机器人工作区域有独立于 API 的硬安全回路。
把 API 控制当成纯软件问题来做,最后一定会被现场安全要求打回来。第一版代码就从安全兜底做起,看似多写代码,实际上是省掉后续半夜进车间排障最有效的习惯。希望帮到你。
本文还有配套的精品资源,点击获取