11 KiB
瓴控 MS3008 双电机 HTTP 控制服务
该项目使用 uv 管理 Python 环境,通过 python-can 的
canalystii/PyUSB 后端直连 CANalyst-II,并提供 HTTP 接口控制
广播模式下的两台瓴控 MS3008 电机。
当前现场配置为:
- CAN 波特率:
1 Mbps - 电机 ID:
1,2 - 广播控制帧:标准帧
0x280,DLC 8 - 持续广播频率:
10 Hz - HTTP
open:ID 1 =+400raw,ID 2 =-400raw - HTTP
close:ID 1 =-400raw,ID 2 =+400raw - 服务启动:自动 Enable;正常退出:自动 Stop 后 Disable
- MS3008 开环 raw 范围:
-850..+850
同时控制 ID 1、2 时,固定控制帧分别为:
open:0x280 90 01 70 FE 00 00 00 00close:0x280 70 FE 90 01 00 00 00 00
400是 MS3008 的协议原始开环控制量,不是力矩,也不是 N·m。 正负号只表示协议方向;机械正反方向必须逐台、低值、短脉冲实测确认。 上述映射是方向确认后的目标值,禁止将其用于首次上电试转。
1. 硬件和协议边界
- 两台电机必须已用厂商上位机设为广播模式、
1 Mbps,ID 分别为 1 和 2。 0x280的四个 16 位小端槽位依次对应 ID 1..4。本现场使用前两个槽位, ID 3、4 槽位写 0。总线上不要同时运行另一个广播控制程序。- MS3008 的
0x280是开环控制命令,不是 MF/MG 的力矩电流命令;不得用 “力矩 400”“400 N·m”等方式描述或标定。 - 每次 HTTP Stop 会显式执行两轮“零控制量 + 协议 Stop”,间隔
20 ms; 每轮的 Stop 帧最多等待100 ms让 CANalyst-II TX 队列处理完。 这仍不等于电机 ACK、物理急停、STO 或断电。 MOTOR_HARDWARE_ENABLED=true时,服务启动自动发送零输出和0x88Enable; HTTP 不再提供enable/disable接口。- 服务正常退出会先执行两轮 Stop,再发送
0x80Disable。0x80会清除电机多圈计数;SIGKILL、进程崩溃或断电时无法保证执行退出清理。 普通暂停应调用stop;open/close换向不会 Disable。 - USB 设备打开或
send()返回不代表电机已实际转动;可在 HTTP 服务停止时使用独立手动脚本读取状态回包作为可选诊断。
2. 用 uv 创建环境
需要 Python 3.11 或更高版本,并先安装 uv。在本目录执行:
uv sync --locked
uv run --locked pytest
uv sync 会创建 .venv 并按照 uv.lock 安装依赖。如果项目位于不支持虚拟环境
软链接的 Samba/NAS 挂载盘,可以把环境放到本地磁盘;后续 uv run 也要保留
同一个变量:
export UV_PROJECT_ENVIRONMENT=/tmp/lingkong-motor-service-venv
uv sync --locked
uv run --locked pytest
3. CANalyst-II 直连后端
当前设备为 VID:PID 04d8:0053。它使用厂商自定义的 USB 协议,
不会自动生成 SocketCAN canX 接口。服务固定使用
python-can[canalystii]:
MOTOR_CAN_CHANNEL=0
MOTOR_CAN_DEVICE=0
MOTOR_CAN_BITRATE=1000000
通道号与面板标识的对应关系为:
- 面板
CAN1→MOTOR_CAN_CHANNEL=0 - 面板
CAN2→MOTOR_CAN_CHANNEL=1 MOTOR_CAN_DEVICE=0表示 PyUSB 枚举到的第一块 CANalyst-II,与通道号无关。
可只读检查 USB 设备:
lsusb -d 04d8:0053
lsusb -t
该 USB interface 不应绑定 usbserial_generic,也不应把强制生成的
ttyUSB0..5 当成可靠 CAN 通道。如果 PyUSB 报权限错误,应为
04d8:0053 设置范围明确的 udev 权限规则,不要以 root 长期运行 HTTP 服务。
CAN 接线检查
断电后检查:
- 总线两端各有约
120 Ω终端,CAN_H 与 CAN_L 之间实测应约为60 Ω。 - USB-CAN 与电机控制侧信号地可靠共地。
- CAN_H 对 CAN_H、CAN_L 对 CAN_L,不能接反。
- 采用总线型连接并尽量缩短支线;
1 Mbps对布线和终端更敏感。
该后端不能可靠报告 CAN ACK、bus-off 或真实发送失败;停止时的
TX 队列等待只证明设备队列已处理,不证明电机已收到或机械已停。因此服务的
can_connected 只表示 USB 对象已打开。独立手动状态脚本可读取 ID 1、2 的
0x9C 回包作为可选诊断。
4. 服务配置与启动
复制配置:
cp .env.example .env
.env.example 已对应当前 MS3008 双电机现场,关键项为:
MOTOR_HARDWARE_ENABLED=false
MOTOR_CAN_CHANNEL=0
MOTOR_CAN_DEVICE=0
MOTOR_CAN_BITRATE=1000000
MOTOR_IDS=1,2
MOTOR_OPEN_CONTROL_RAW=400
MOTOR_CLOSE_CONTROL_RAW=400
MOTOR_BROADCAST_FREQUENCY_HZ=10
MOTOR_HTTP_HOST=0.0.0.0
MOTOR_HTTP_PORT=8000
MOTOR_API_TOKEN=
MOTOR_OPEN_CONTROL_RAW 和 MOTOR_CLOSE_CONTROL_RAW 是幅值;程序按电机 ID
固定方向:open 为 {1: +400, 2: -400},close 为
{1: -400, 2: +400}。
完成接线和物理安全准备后,将 MOTOR_HARDWARE_ENABLED 改为 true。
此开关是启动安全总闸:服务启动后会立即自动 Enable,无需 HTTP 调用。
程序不会自动读取 .env,启动前导入:
set -a
. ./.env
set +a
uv run --locked lingkong-motor-service
也可直接一键启动(自动加载 .env、同步锁定依赖并检查端口冲突):
./script/launch.sh
如果 .env 不存在,脚本会根据 .env.example 创建一份,完成硬件检查并设置
MOTOR_HARDWARE_ENABLED=true 后再次运行。
服务默认监听 0.0.0.0:8000,即全部 IPv4 网络接口。本机可打开
http://127.0.0.1:8000/docs,其他电脑应访问
http://<服务器真实局域网IP>:8000/docs;0.0.0.0 只是监听地址,不能作为客户端目标。
当前 MOTOR_API_TOKEN 留空,不启用 API Key,任何能访问 TCP 8000 端口的电脑都可调用
电机接口。请只在可信局域网中使用,并用主机或网络防火墙限制可访问范围。
真实硬件上只能运行一个服务进程,不要使用 --reload 或多个 worker。
5. 首次实机测试顺序
必须先逐台验证,不能直接以 ID 1,2 和目标 raw 首次试转:
- 断开负载并架空输出轴,设置电源限流,确认物理急停和断电回路有效。
- 在 HTTP 服务未运行时,可选执行
uv run --locked python tests/test_can.py status,观察 ID 1、2 的0x9C状态回包作为独立诊断。运行该脚本时不得同时启动 HTTP 服务。 - 临时设置
MOTOR_IDS=1,将两个方向的控制量都改为20。服务启动并自动使能后只调用一次open(服务内部会以 10 Hz 持续广播 ID 1 =+20),约100 ms时必须显式调用stop; 确认完全停稳后,才以同样方式测试close的 ID 1 =-20/100 ms,再显式stop、停稳并 最后用Ctrl-C正常退出,由服务自动 Stop 并 Disable。 - 保持 raw
20,把MOTOR_IDS改为2,按 ID 2open=-20、close=+20的预期验证第二台电机。 - 两台的状态、方向和停机都确认后,才逐级提高控制量。每一级、每个方向之间
都必须 Stop 并确认机械完全停稳,最终映射为
open={1:+400,2:-400}、close={1:-400,2:+400}。 - 首次方向验证仍必须执行“当前方向 → Stop → 确认停稳 → 另一方向”。正常运行时
open → close和close → open都可直接调用;服务会自动停止当前广播,发送零输出 与 Stop,等待20 ms后开始另一方向广播。该等待不代表机械轴已经停稳。
100 ms 是上限目标,不是普通 shell/HTTP 调度的硬实时保证。旁站人员必须一直
可以操作物理急停;必须由操作者显式调用 HTTP stop,且 HTTP stop 不能作为唯一保护。
手动 CAN 测试脚本
tests/test_can.py 是一个不依赖项目其他脚本的简单手动测试程序。
它直接使用 python-can 的 canalystii 后端,默认面板 CAN1、1 Mbps、电机 ID 1和2。
运行前要停止 HTTP 服务。
读取状态2(0x9C):
uv run --locked python tests/test_can.py status
发送 open 映射(ID 1 = +400、ID 2 = -400,持续 10 Hz,
Ctrl-C 后自动零输出并 Stop):
uv run --locked python tests/test_can.py torque 400 -400
发送 close 映射(ID 1 = -400、ID 2 = +400,持续 10 Hz,
Ctrl-C 后自动零输出并 Stop):
uv run --locked python tests/test_can.py torque -400 400
停止 ID 1、2:
uv run --locked python tests/test_can.py stop
仅测试一台时,另一台的值填 0,例如 torque 20 0。MS 的该控制值是开环
powerControl raw,不是 N·m 力矩。
6. HTTP 接口
完成上述低值方向验证后,正常操作顺序如下:
SERVER_IP="<服务器真实局域网IP>"
curl -X POST "http://${SERVER_IP}:8000/v1/motor/open"
# 方向切换会自动执行零输出、Stop 和 20 ms 等待
curl -X POST "http://${SERVER_IP}:8000/v1/motor/close"
curl -X POST "http://${SERVER_IP}:8000/v1/motor/open"
curl -X POST "http://${SERVER_IP}:8000/v1/motor/stop"
curl "http://${SERVER_IP}:8000/v1/motor/status"
open、close、stop 成功时均返回 HTTP 200 OK,响应 JSON 包含
"code": 200、执行动作和当前电机状态。例如:
{
"code": 200,
"accepted": true,
"action": "stop",
"state": "stopped"
}
实际响应还包含电机 ID、控制量、广播状态等字段。硬件未启用或状态冲突仍返回
HTTP 409,CAN/USB 通讯失败仍返回 HTTP 503。
MOTOR_HARDWARE_ENABLED=true 时,启动过程会自动 Enable。HTTP 只保留
open、close、stop 和 status,不提供 enable/disable。
open 固定使用 {1:+400,2:-400},close 固定使用
{1:-400,2:+400};HTTP 请求不能临时提高 raw。状态响应中
control_raw_by_motor 显示每台电机的实际有符号控制量;保留的 control_raw
字段表示 ID 1(单独配置 ID 2 时表示 ID 2)。调用任一运动接口后,服务内部线程都会以
MOTOR_BROADCAST_FREQUENCY_HZ
(默认 10 Hz)持续发送完整 0x280 四槽帧,不会自动超时,HTTP 客户端断开也不会
停止。open 与 close 相互切换时会自动执行零输出与 Stop;最终仍必须显式调用
stop。单次 stop 会自动重复两轮停止序列,不需要客户端再调用两次。
服务正常退出会自动执行两轮 Stop,再 Disable 并关闭 CAN 设备。
当前配置不需要 API Key。如果以后显式设置了 MOTOR_API_TOKEN,请求才需携带:
curl -X POST -H "X-API-Key: your-token" \
"http://${SERVER_IP}:8000/v1/motor/open"
7. 验证边界
自动化测试只验证配置、协议编码、HTTP 状态机和持续广播逻辑,不证明电机已经运动、 方向正确、状态回包可信或急停有效。真实 MS3008 验证结果必须单独记录,并包含 电机 ID、回包、raw、脉冲时长、停止结果和 USB/CAN 错误计数。