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(不入库)。
123 lines
4.7 KiB
Python
123 lines
4.7 KiB
Python
"""硬件变量 + 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()
|