跳到主要内容

NexTap机械手

NexTap i8 龙门机械手操作多台手机官方现货点击放大
MECHANICAL PHYSICAL TOUCH
NexTap i8 龙门机械手

触控头直接点击真实屏幕,为多工位手机自动化提供稳定的物理触控。

模拟人手点击终极方案
8 工位随意摆放,自动校准
±3 px点击精度
固件远程升级

NexTap 是通过龙门机械手直接操作手机屏幕的真实触控方案,适用于需要绕过软件触控限制、执行实体屏幕操作的自动化场景。

模块​

from ascript.ios import nextap
使用前说明
  • 首次使用时,请在 AScript App 中手动连接 NexTap,并完成设备回零和对应手机工位的校准;后续调用 Python API 会自动连接。
  • 坐标使用当前手机屏幕的物理像素坐标,原点为左上角,X 向右增加,Y 向下增加。
  • 调用运动 API 前,当前手机必须完成对应方向的 XY 校准;调用 Z 轴点击 API 还需要完成该手机独立的 Z 校准。
  • 运动和状态 API 返回 Python dict,调用方应检查返回值中的 ok 字段。事务锁 API 的返回值不同:acquire_lock() 返回 token,lock() 返回上下文管理器。

成功或失败的设备响应通常包含以下字段:

{
"ok": True,
"action": "move",
"message": "...",
"connected": True,
"name": "...",
"data": {},
}

底层返回 JSON 字符串时,模块会自动解析;无法解析时返回:

{"ok": False, "message": "invalid nextap response: ..."}

状态查询​

读取连接、初始化、校准、当前位置、限位、回零和方向状态。

nextap.status()

status() 是推荐写法,statue() 是兼容旧代码的别名:

nextap.status()
nextap.statue()

XY 移动​

移动到屏幕坐标​

移动到当前手机屏幕的绝对像素坐标 (x, y)。

nextap.move(x, y, duration_ms=0)
参数类型是否必填说明
xint是目标屏幕 X 坐标。
yint是目标屏幕 Y 坐标。
duration_msint否请求移动时长,默认 0,允许范围 0..30000ms。为 0 时使用 ESP32 当前全局 XY 基础速度。
  • 坐标必须在当前校准方向的有效屏幕范围内,否则返回 RANGE。
  • duration_ms > 0 时,App 会根据目标距离和请求时长计算显式移动速度。
  • 运动由 ESP32 步进规划执行,包含加速、匀速、减速和目标停稳。
  • 该方法是同步 API,返回时本次运动命令已经结束或失败。

示例:

from ascript.ios import nextap

# 使用当前基础速度移动
nextap.move(947, 2079)

# 请求在 800 毫秒内完成移动
nextap.move(947, 2079, duration_ms=800)
警告

nextap.move(0, 0) 表示移动到屏幕左上角,不是执行机械 HOME。

点击​

原子点击动作。固件会在一个运动任务中完成 XY 定位、Z 下压、保持和抬起,不会让多个 Python 调用交叉执行。

nextap.click(x, y, dur=20, count=1, interval=150)
参数类型是否必填说明
xint是点击屏幕 X 坐标。
yint是点击屏幕 Y 坐标。
durint否Z 轴接触保持时间,默认 20ms,允许范围 1..3000ms。
countint否点击次数,默认 1;大于等于 2 时在同一坐标执行多击。
intervalint否多击之间的目标开始时间间隔,允许范围 40..300ms。

多击总请求时间不能超过 10000ms。动作流程为:

XY 加速移动 -> 减速 -> 回差补偿和低速精定位 -> 停稳
-> 使用当前手机独立 Z 接触位置下压约 2mm
-> 保持 dur 毫秒 -> 自动抬起

示例:

from ascript.ios import nextap

# 单击
nextap.click(661, 1398)

# 点击并保持 20 毫秒
nextap.click(661, 1398, dur=20)

# 双击
nextap.click(661, 1398, count=2, interval=150)

双击​

双击快捷方法,等价于在同一个坐标调用一次 click() 并设置 count=2。

nextap.double_click(x, y, dur=20, interval=150)

等价写法:

nextap.click(x, y, dur=dur, count=2, interval=interval)

同一个坐标只执行一次 XY 定位。

贝塞尔滑动​

滑动​

从 (x, y) 滑动到 (x1, y1)。参数顺序与 action.slide 保持一致;默认时长为 300ms。

nextap.slide(
x, y, x1, y1,
dur=300,
interpolator=None,
control_points=None,
)
参数类型是否必填说明
x、yint是滑动起点坐标。
x1、y1int是滑动终点坐标。
durint否滑动时长,默认 300ms,允许范围 50..5000ms。
interpolatorstr、tuple、list 或 None否速度曲线,默认 None,表示线性速度。
control_pointslist、int 或 None否贝塞尔控制点。None 或空列表表示直线。

滑动规则:

  • 显式控制点格式为 [c1x, c1y, c2x, c2y, ...]。
  • [c1x, c1y] 表示一个控制点的二次贝塞尔曲线。
  • [c1x, c1y, c2x, c2y] 表示两个控制点的三次贝塞尔曲线。
  • 当前固件和 BLE 协议最多支持 8 组控制点,即数组最多 16 个数字。
  • 控制点也必须落在当前校准屏幕范围内,否则返回 RANGE。
  • 滑动由 ESP32 按贝塞尔采样点执行,每段仍使用步进器加减速,不会瞬间启动或急停。

示例:

from ascript.ios import nextap

# 直线滑动
nextap.slide(100, 300, 800, 1200)

# 二次贝塞尔曲线:一个控制点
nextap.slide(100, 300, 800, 1200, control_points=[400, 600])

# 三次贝塞尔曲线:两个控制点
nextap.slide(100, 300, 800, 1200,
control_points=[300, 400, 600, 900])

# 使用加速曲线
nextap.slide(100, 300, 800, 1200,
dur=600,
interpolator=("accelerate", 1.5))

支持的插值器名称:

linear
accelerate
decelerate
accelerate_decelerate

也可以写成带因子的字符串,例如:

nextap.slide(100, 300, 800, 1200,
interpolator="accelerate:1.5")

因子范围是 0.1..5.0。iOS 端支持字符串、(name, factor) 元组或 [name, factor] 列表。

整数控制点​

为了模拟人手抖动,control_points 也可以传入整数,由 Python 层生成随机控制点:

# 根据距离自动生成至少 1 组随机控制点
nextap.slide(100, 300, 800, 1200, control_points=0)

# 请求生成 3 组随机控制点
nextap.slide(100, 300, 800, 1200, control_points=3)

整数 <= 0 会根据距离自动决定至少 1 组;正整数表示请求的随机控制点组数。当前上限是 8 组,因此 control_points=100 会报参数错误。

Z 轴​

下压​

使用当前手机独立的 Z 校准数据下压,包含约 2mm 的接触后过行程,保持指定时间后自动抬起。

nextap.down(duration_ms=20)
参数类型是否必填说明
duration_msint否接触保持时间,默认 20ms,允许范围 1..3000ms。

该方法只控制 Z 轴,不移动 XY,也不经过 XY 龙门的路径规划。

抬起​

抬起触控头并释放当前设备锁,只控制 Z 轴,不移动 XY。

nextap.up()

运动速度和规划​

XY 速度来源​

  1. move(..., duration_ms=0) 和点击的 XY 定位使用 ESP32 全局 feed。
  2. NexTap 页面中的 XY 基础速度滑条会通过 SET feed 更新该值,当前软件滑条范围是 40..300mm/s。
  3. move(..., duration_ms>0) 根据时长计算显式速度,不直接使用滑条值。
  4. slide 根据总时长和曲线段长度计算各段速度,同时受固件速度和加速度限制。

固件规划效果​

普通 XY 目标移动和点击前定位都会经过:

加速 -> 目标速度附近运行 -> 减速 -> 精定位 -> 短暂停稳

短距离可能尚未达到最高速度就开始减速。设置较高基础速度不代表机械一定能稳定达到该速度,实际速度还受 g_cfg.accel、负载、皮带和步进电机能力影响。

常见错误​

RANGE​

坐标超出当前手机当前方向的校准分辨率,或控制点超出范围。错误通常包含:

orientation=0 res=1080x2400

这表示当前使用竖屏坐标系,合法像素约为 0..1079、0..2399。横屏必须使用已校准的横屏方向和对应分辨率。

设备正在执行上一条命令​

前一条 BLE 运动仍在执行,或者多个循环同时向同一控制器发起动作。运动 API 在 App 内部有原子锁,但调用方仍应避免无延时地启动多个独立脚本循环。

推荐在连续测试循环中检查返回值并适当等待:

import time
from ascript.ios import nextap

while True:
result = nextap.move(947, 2079)
if not result.get("ok", False):
print(result)
time.sleep(0.1)

与 HOME 的区别​

设备未回零时,请先在 NexTap 页面完成 HOME。屏幕坐标 API 与机械回零是两套不同的操作。

move(0, 0) 表示移动到当前 iPhone 或 iPad 屏幕左上角,绝不是 HOME,也不是物理限位归零。

多动作事务锁​

事务锁用于把多次 Python API 调用组成一个不可被其他手机或手动操作穿插的连续动作。单独调用 move()、click() 或 slide() 时无需手动加锁;只有多条 API 必须连续执行时才需要显式事务。

lock​

nextap.lock(timeout=10, lease=30)

返回事务上下文管理器,推荐配合 with 使用。代码块正常结束或抛出异常时都会自动释放锁。

参数类型是否必填说明
timeoutint 或 float否等待取得全局龙门锁的最长秒数,默认 10,允许范围 0.1..120。
leaseint 或 float否取得锁后允许持有的最长秒数,默认 30,允许范围 0.1..30。

推荐写法:

from ascript.ios import nextap

with nextap.lock(timeout=10, lease=30):
result = nextap.move(661, 1398)
if not result.get("ok", False):
raise RuntimeError(result.get("message", "移动失败"))

result = nextap.down(20)
if not result.get("ok", False):
raise RuntimeError(result.get("message", "点击失败"))

上例保证 move() 和 down() 连续执行。即使任一动作失败并抛出异常,退出 with 时也会释放事务锁。

租约时间

lease 不会自动续期,应大于代码块的预计总执行时间。不要在事务块中执行网络请求、长时间 sleep 或无限循环。

acquire_lock​

token = nextap.acquire_lock(timeout=10, lease=30)

同步等待取得全局龙门锁。成功时返回字符串 token;等待超时、自动连接失败或设备拒绝时抛出 RuntimeError。

release_lock​

result = nextap.release_lock(token=None)

释放当前 Python 线程取得的事务锁并返回 dict。省略 token 时使用当前线程保存的 token;重复释放会按幂等成功处理。

手动管理锁时,必须在 finally 中释放:

from ascript.ios import nextap

token = nextap.acquire_lock(timeout=10, lease=30)
try:
nextap.move(661, 1398)
nextap.down(20)
finally:
nextap.release_lock(token)

事务锁行为:

  • 多台手机同时等待时,由 ESP32 按等待顺序授予锁。
  • 事务块内的运动命令会携带 token,其他手机以及当前手机页面上的手动运动不能插入。
  • 租约到期后,下一条事务内动作会返回锁失效,不会脱离锁继续执行。
  • Python 进程崩溃时由租约到期释放;蓝牙断开时 ESP32 会立即释放。
  • 已经开始的单条原子动作会安全完成,不会因租约中途到期而把 Z 轴停在屏幕上。
  • STOP 急停始终可以越过事务锁执行。

并发建议​

  • move()、click() 和 slide() 是同步调用,Python 会等待本次动作成功或失败后再返回。
  • 多台手机同时循环调用单个原子 API 时,ESP32 会串行处理,不会拆分一条动作。
  • 将 move() 与 down() 分开调用时,两条命令之间可能轮到其他手机;需要保证连续执行时,请使用 with nextap.lock(...)。
  • 单点操作优先使用 click(),滑动优先使用 slide();它们本身就是不可穿插的完整原子动作。