feat(pybot): bot4cj 的 Python 迁移版(.mt 解析 + 硬件驱动 + 插件系统)
- parser: FSM 解析器与仓颉版逐语义对齐,修 S01/S02/S04/Q05 - robot/hardvars: 六类硬件变量工厂,修 r.cj:911 重名 FIXME、Q07 倒序释放 - plugin: Python 插件替代 Lua 桥接,修 S07 三级路径查找 - hd: i2c(每fd锁 Q03/S06)/spi/servo/kalman/mpu6050(S05/Q06)/speaker(S03 实声) - modules: PCA9685 与 Lua 版逐字节对齐;ADS1115 重写(experimental);ADS1256 空桩 - tests: 42 项 pytest 全绿(Mock 总线 journal 断言) - CLI: --dry-run / --test roundtrip / --verbose;运行时零第三方依赖
This commit is contained in:
1 parent
85eaa2114e
commit
bba7ee3eb4
31 files changed
+2155
No files matched your search
@@ -0,0 +1,56 @@
|
||||
"""ADS1115 16 位 I2C ADC 插件(experimental)
|
||||
|
||||
⚠️ bot4cj/modules/ADS1115.lua 是 PCA9685 的复制残片(引用未定义的
|
||||
prescale、写的是 PCA9685 寄存器,运行必错),无忠实移植价值。
|
||||
本文件按 ADS1115 数据手册写干净的最小单次转换实现,未经实机验证。
|
||||
|
||||
ctx 元数据:name / fd(I2C 引用展开)/ address(如 "0x48")
|
||||
call 实时参数:channel(0~3,单端输入 AINx)
|
||||
call 返回:{"raw": 有符号 16 位原始值, "voltage": 电压(±4.096V 量程)}
|
||||
"""
|
||||
|
||||
import logging
|
||||
import struct
|
||||
import time
|
||||
|
||||
from pybot.hd import i2c
|
||||
|
||||
log = logging.getLogger("pybot.modules.ADS1115")
|
||||
|
||||
REG_CONVERSION = 0x00
|
||||
REG_CONFIG = 0x01
|
||||
|
||||
# 单次转换配置:OS=1(开始) | MUX=100+ch(单端) | PGA=001(±4.096V) | MODE=1(单次)
|
||||
# 低字节:DR=100(128SPS) | 比较器禁用(11)
|
||||
_CONFIG_HI_BASE = 0b1_100_001_1 # channel 0,其余通道 MUX +ch
|
||||
_CONFIG_LO = 0b100_0_0_0_11
|
||||
FSR = 4.096 # 满量程 ±4.096V
|
||||
|
||||
|
||||
def build(ctx):
|
||||
"""无需初始化(单次转换模式按需触发),仅校验设备可达"""
|
||||
fd = int(ctx["fd"])
|
||||
addr = int(str(ctx["address"]), 0)
|
||||
log.info("ADS1115 build (experimental): fd=%d addr=0x%02X", fd, addr)
|
||||
return {"result": "OK"}
|
||||
|
||||
|
||||
def call(ctx):
|
||||
"""触发一次单次转换并读回结果"""
|
||||
fd = int(ctx["fd"])
|
||||
addr = int(str(ctx["address"]), 0)
|
||||
channel = int(ctx.get("channel", 0)) & 0x03
|
||||
config_hi = _CONFIG_HI_BASE + (channel << 4)
|
||||
with i2c.transaction(fd):
|
||||
i2c.set_addr(fd, addr)
|
||||
i2c.write_block(fd, REG_CONFIG, bytes([config_hi, _CONFIG_LO]))
|
||||
time.sleep(0.009) # 128SPS 一次转换约 8ms
|
||||
data = i2c.read_block(fd, REG_CONVERSION, 2)
|
||||
raw = struct.unpack(">h", data)[0] # 大端有符号 16 位
|
||||
return {"result": "OK", "raw": raw, "voltage": raw * FSR / 32768.0}
|
||||
|
||||
|
||||
def destroy(ctx):
|
||||
"""单次转换模式空闲即低功耗,无需复位"""
|
||||
log.info("ADS1115 destroy")
|
||||
return {"result": "OK"}
|
||||
@@ -0,0 +1,24 @@
|
||||
"""ADS1256 24 位 SPI ADC 插件 —— 对应 bot4cj/modules/ADS1256.lua(空桩)
|
||||
|
||||
Lua 版即为空桩(无任何实现),保持同等状态:三个入口仅记日志。
|
||||
实机接入时参考 modules/SPI.py 取 fd 后经 pybot.hd.spi.transfer 实现。
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("pybot.modules.ADS1256")
|
||||
|
||||
|
||||
def build(ctx):
|
||||
log.warning("ADS1256 is a stub (not implemented), build ctx: %s", ctx)
|
||||
return {"result": "OK", "status": "stub"}
|
||||
|
||||
|
||||
def call(ctx):
|
||||
log.warning("ADS1256 is a stub (not implemented)")
|
||||
return {"result": "OK", "status": "stub"}
|
||||
|
||||
|
||||
def destroy(ctx):
|
||||
log.warning("ADS1256 is a stub (not implemented)")
|
||||
return {"result": "OK", "status": "stub"}
|
||||
@@ -0,0 +1,72 @@
|
||||
"""PCA9685 16 路 PWM 舵机控制器插件 —— 与 bot4cj/modules/PCA9685.lua 逐语义对齐
|
||||
|
||||
pybot 插件协议:build(ctx) / call(ctx) / destroy(ctx)
|
||||
ctx 元数据来自 @PCA9685 块:
|
||||
name / fd(I2C 引用展开)/ address(原始字符串如 "0x40")
|
||||
call 实时参数:channel(int)/ angle(int,0~180)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from pybot.hd import i2c
|
||||
|
||||
log = logging.getLogger("pybot.modules.PCA9685")
|
||||
|
||||
# MODE1 寄存器(0x00)控制位
|
||||
MODE1 = 0x00
|
||||
MODE1_RESTART = 0x80
|
||||
MODE1_SLEEP = 0x10
|
||||
MODE1_AUTOINC = 0x20
|
||||
PRE_SCALE = 0xFE
|
||||
LED0_ON_L = 0x06 # 通道 0 PWM 寄存器组起点,每通道 4 个寄存器
|
||||
|
||||
# 50Hz 预分频:25MHz / (4096 * 50Hz) - 1,四舍五入 = 121(与 Lua 版一致)
|
||||
PRESCALE_50HZ = int(25000000.0 / (4096.0 * 50.0) - 1.0 + 0.5)
|
||||
|
||||
|
||||
def _fd_addr(ctx):
|
||||
return int(ctx["fd"]), int(str(ctx["address"]), 0)
|
||||
|
||||
|
||||
def build(ctx):
|
||||
"""初始化:重启 → SLEEP → 设 prescale(50Hz) → 唤醒(寄存器序列与 Lua 版一致)"""
|
||||
fd, addr = _fd_addr(ctx)
|
||||
log.info("PCA9685 build: fd=%d addr=0x%02X prescale=%d",
|
||||
fd, addr, PRESCALE_50HZ)
|
||||
with i2c.transaction(fd):
|
||||
i2c.set_addr(fd, addr)
|
||||
i2c.write_reg(fd, MODE1, MODE1_RESTART) # 复位
|
||||
time.sleep(0.005)
|
||||
i2c.write_reg(fd, MODE1, MODE1_SLEEP) # 休眠(改 prescale 前提)
|
||||
i2c.write_reg(fd, PRE_SCALE, PRESCALE_50HZ) # 50Hz
|
||||
i2c.write_reg(fd, MODE1, MODE1_AUTOINC) # 唤醒 + 自动递增
|
||||
time.sleep(0.005)
|
||||
return {"result": "OK"}
|
||||
|
||||
|
||||
def call(ctx):
|
||||
"""写通道 PWM:0.5ms(0°) ~ 2.5ms(180°) 脉宽 @ 50Hz(20ms 周期)"""
|
||||
fd, addr = _fd_addr(ctx)
|
||||
channel = int(ctx["channel"])
|
||||
angle = float(ctx["angle"])
|
||||
width = 0.5 + 2.0 * angle / 180.0 # 脉宽 ms
|
||||
offval = int(width * 4096.0 / 20.0) # 20ms 周期内的计数(floor)
|
||||
reg = LED0_ON_L + 4 * channel
|
||||
with i2c.transaction(fd):
|
||||
i2c.set_addr(fd, addr)
|
||||
i2c.write_reg(fd, reg + 0, 0x00) # ON_L
|
||||
i2c.write_reg(fd, reg + 1, 0x00) # ON_H
|
||||
i2c.write_reg(fd, reg + 2, offval & 0xFF) # OFF_L
|
||||
i2c.write_reg(fd, reg + 3, (offval >> 8) & 0xFF) # OFF_H
|
||||
return {"result": "OK"}
|
||||
|
||||
|
||||
def destroy(ctx):
|
||||
"""复位 PCA9685"""
|
||||
fd, addr = _fd_addr(ctx)
|
||||
log.info("PCA9685 destroy: fd=%d addr=0x%02X", fd, addr)
|
||||
with i2c.transaction(fd):
|
||||
i2c.set_addr(fd, addr)
|
||||
i2c.write_reg(fd, MODE1, MODE1_RESTART)
|
||||
return {"result": "OK"}
|
||||
@@ -0,0 +1,42 @@
|
||||
"""SPI 总线插件 —— 与 bot4cj/modules/SPI.lua 逐语义对齐
|
||||
|
||||
@SPI 块元数据:name / path / spi_mode / bits / speed / delay
|
||||
build 打开并配置 spidev;call 返回 {"fd": fd}(供其他块引用展开);
|
||||
destroy 关闭。模块级全局 _fd 即设备私有状态
|
||||
(pybot 每设备加载独立模块实例,与 Lua 每设备独立状态机等价)。
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from pybot.hd import spi
|
||||
|
||||
log = logging.getLogger("pybot.modules.SPI")
|
||||
|
||||
_fd = -1 # 全局文件句柄(对应 SPI.lua 的 fd)
|
||||
|
||||
|
||||
def build(ctx):
|
||||
global _fd
|
||||
for key in ("path", "spi_mode", "bits", "speed", "delay"):
|
||||
if key not in ctx:
|
||||
return {"result": "ERR", "message": f"Invalid Arguments: {key}"}
|
||||
log.info("SPI build parameters: %s", ctx)
|
||||
_fd = spi.open_dev(ctx["path"])
|
||||
if _fd <= 0:
|
||||
return {"result": "ERR", "message": "Failed to open SPI file"}
|
||||
spi.setup(_fd, int(str(ctx["spi_mode"]), 0),
|
||||
int(ctx["bits"]), int(ctx["speed"]))
|
||||
return {"result": "OK", "fd": _fd}
|
||||
|
||||
|
||||
def call(ctx):
|
||||
return {"fd": _fd}
|
||||
|
||||
|
||||
def destroy(ctx):
|
||||
global _fd
|
||||
log.info("SPI destroy, fd=%d", _fd)
|
||||
if _fd > 0:
|
||||
spi.close_dev(_fd)
|
||||
_fd = -1
|
||||
return {"result": "OK"}
|
||||
Reference in new issue
Block a user