Files
qing 99c1bc1739 fix(pybot×actpred): 微动作录制时间轴均匀化——首行分解为重复步,delay 列权重归零
pybot run():带 delay 的首行(无前序状态)不再单步执行,分解为
N=delay//10 个重复微步(插值退化恒等),录制数据全 10ms 均匀节拍,
delay 列恒 10ms——行级时长信息已隐式编码在步数中,消除首行 200ms
造成的时间采样不均(frame_len/history_len 等间隔假设被破坏)。

actpred default_column_weights:spk.delay 权重 0.2→0.0(步级恒值列
无信号,与保留位同级);spk.F 保持 0.2。

新录制数据集(952 样本,delay 恒 10)存于 data/pybot_record_v2(不入库)。
2026-08-14 21:56:01 +00:00

123 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""硬件变量 + RobotStruct 全流程测试(Mock I2C 总线,journal 断言)
关键期望值(与 bot4cj + PCA9685.lua 逐字节对齐):
45° → 脉宽 0.5+2*45/180 = 1.0ms → offval = int(1.0*4096/20) = 204
135° → 脉宽 2.0ms → offval = int(2.0*4096/20) = 409(s3 反装 45°→135°)
0° → 脉宽 0.5ms → offval = 102(首行 '-' 占位解析为 0)
"""
from pybot.robot import RobotStruct
from tests.conftest import SCRIPTS_DIR
PRESCALE = 121 # int(25e6/(4096*50) - 1 + 0.5)
def std_robot(mock_i2c):
with open(SCRIPTS_DIR / "std.mt", encoding="utf-8") as f:
return RobotStruct(f, dry_run=True)
def _writes(mock, fd):
return [(e[2], e[3]) for e in mock.journal
if e[0] == "write_reg" and e[1] == fd]
def test_create_hardvars(mock_i2c):
robot = std_robot(mock_i2c)
assert set(robot.M) == {"A", "F", "i2c-0", "i2c-1", "pca9685-0", "spk",
"s1", "s2", "s3", "s4", "s5",
"s6", "s7", "s8", "s9", "s10"}
# 两条 I2C 总线在工厂阶段即打开
opens = [e for e in mock_i2c.journal if e[0] == "open"]
assert [e[1] for e in opens] == ["/dev/i2c-0", "/dev/i2c-1"]
# 别名变量
assert robot.M["A"].play({}) == "angle"
assert robot.M["F"].play({}) == "frequency"
robot.deactive()
def test_pca9685_build_sequence(mock_i2c):
robot = std_robot(mock_i2c)
fd = robot.M["i2c-1"].play({})["fd"]
robot.active()
# 初始化序列:复位 → SLEEP → prescale=121 → 唤醒(与 Lua 版一致)
assert _writes(mock_i2c, fd) == [
(0x00, 0x80), (0x00, 0x10), (0xFE, PRESCALE), (0x00, 0x20)]
assert ("set_addr", fd, 0x40) in mock_i2c.journal
robot.deactive()
def test_run_stand_action(mock_i2c):
robot = std_robot(mock_i2c)
fd = robot.M["i2c-1"].play({})["fd"]
robot.active()
mock_i2c.journal.clear() # 只看 @Action 阶段
robot.run()
writes = _writes(mock_i2c, fd)
reg = lambda ch: 0x06 + 4 * ch # noqa: E731
def servo_writes(ch, offval):
return [(reg(ch) + 0, 0x00), (reg(ch) + 1, 0x00),
(reg(ch) + 2, offval & 0xFF), (reg(ch) + 3, offval >> 8)]
# s1: 45° 正装 → offval 204;s2: '-' 首行 → 0° → 102
assert writes[0:4] == servo_writes(0, 204)
assert writes[4:8] == servo_writes(1, 102)
# s3: max=-90 反装,45° → 135° → offval 409(写 [153, 1])
assert writes[8:12] == servo_writes(2, 409)
# s9/s10: 90° → offval = int(1.5*4096/20) = 307
assert writes[32:36] == servo_writes(8, 307)
assert writes[36:40] == servo_writes(9, 307)
# 首行 delay=200 → 分解为 20 个重复微步:10 舵机 × 4 寄存器 × 20 步
assert len(writes) == 800
assert writes[40:44] == servo_writes(0, 204) # 第 2 步与第 1 步相同
robot.deactive()
def test_deactive_reverse_and_idempotent(mock_i2c):
robot = std_robot(mock_i2c)
fd0 = robot.M["i2c-0"].play({})["fd"]
fd1 = robot.M["i2c-1"].play({})["fd"]
robot.active()
robot.deactive()
# 倒序:先 PCA9685 复位(fd1 写 0x80),再关 i2c-1,最后关 i2c-0
closes = [e[1] for e in mock_i2c.journal if e[0] == "close"]
assert closes == [fd1, fd0]
reset_idx = mock_i2c.journal.index(("write_reg", fd1, 0x00, 0x80),
mock_i2c.journal.index(("close", fd1))
- 5)
assert reset_idx < mock_i2c.journal.index(("close", fd1))
# 幂等:重复调用不再产生操作
n = len(mock_i2c.journal)
robot.deactive()
assert len(mock_i2c.journal) == n
def test_redefined_hardvar_destroys_old(mock_i2c, caplog):
"""重名硬件变量:destroy 旧值 + 告警(修复 r.cj:911 FIXME)"""
text = ("@I2C\nname path;\nx /dev/i2c-0;\nx /dev/i2c-1;\n@\n"
"@Action\nx.angle;\n0;\n@\n")
robot = RobotStruct(text, dry_run=True)
closes = [e for e in mock_i2c.journal if e[0] == "close"]
assert len(closes) == 1 # 旧的 /dev/i2c-0 已被关闭
assert any("redefined" in r.message for r in caplog.records)
robot.deactive()
def test_action_validation(mock_i2c):
"""Q05:执行前整表校验列名格式与设备存在性"""
import pytest
text = ("@I2C\nname path;\ni /dev/i2c-0;\n@\n"
"@Action\nbadcolumn;\n0;\n@\n")
robot = RobotStruct(text, dry_run=True)
with pytest.raises(ValueError, match="not valid"):
robot.run()
robot.deactive()
text2 = ("@I2C\nname path;\ni /dev/i2c-0;\n@\n"
"@Action\nghost.angle;\n0;\n@\n")
robot2 = RobotStruct(text2, dry_run=True)
with pytest.raises(ValueError, match="not defined"):
robot2.run()
robot2.deactive()