117 lines
3.7 KiB
Python
117 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import argparse
|
||
import math
|
||
import struct
|
||
import can
|
||
|
||
|
||
CMD_READ_MULTITURN_ANGLE = 0x92
|
||
|
||
|
||
def arb_id_single(motor_id: int) -> int:
|
||
# 单电机命令/回包 arbitration id
|
||
return 0x140 + int(motor_id)
|
||
|
||
|
||
def build_read_multiturn_angle(motor_id: int) -> can.Message:
|
||
"""
|
||
读取多圈角度命令:
|
||
data[0]=0x92,其余填0
|
||
"""
|
||
data = [CMD_READ_MULTITURN_ANGLE, 0, 0, 0, 0, 0, 0, 0]
|
||
return can.Message(arbitration_id=arb_id_single(motor_id), is_extended_id=False, data=data)
|
||
|
||
|
||
def sign_extend(value: int, bits: int) -> int:
|
||
"""
|
||
对 value 做符号扩展,使其成为 Python int(有符号)
|
||
"""
|
||
sign_bit = 1 << (bits - 1)
|
||
mask = (1 << bits) - 1
|
||
value &= mask
|
||
return (value ^ sign_bit) - sign_bit
|
||
|
||
|
||
def parse_multiturn_angle_reply_int56(msg: can.Message) -> int:
|
||
"""
|
||
兼容你贴的文档:DATA[1..7] 是角度的低->高字节(共7字节)
|
||
=> 解析为有符号 int56
|
||
单位:0.01 deg / LSB
|
||
"""
|
||
d = bytes(msg.data)
|
||
if len(d) != 8 or d[0] != CMD_READ_MULTITURN_ANGLE:
|
||
raise ValueError("not 0x92 reply")
|
||
raw_u56 = int.from_bytes(d[1:8], byteorder="little", signed=False) # 7 bytes
|
||
raw_s56 = sign_extend(raw_u56, 56)
|
||
return raw_s56
|
||
|
||
|
||
def parse_multiturn_angle_reply_int64(msg: can.Message) -> int:
|
||
"""
|
||
如果你确认实际回的是 8 字节 motorAngle(int64):
|
||
常见做法是 DATA[0..7] 都是 motorAngle 的 8 字节(但会与“命令字节=0x92”冲突)
|
||
或者命令字节在别处。
|
||
这个函数留作你确认后切换用。
|
||
"""
|
||
d = bytes(msg.data)
|
||
# 这里假设 d[0] 仍是 0x92,则 motorAngle 应该在 d[1:9] 不存在
|
||
# 所以只有当你的实际报文不是“d[0]=0x92”时才用它
|
||
raise NotImplementedError("Need exact 8-byte layout confirmation.")
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--interface", default="canalystii", help="python-can interface: canalystii / socketcan / ...")
|
||
ap.add_argument("--channel", default="0", help="channel: canalystii 0/1, socketcan can0/can1 ...")
|
||
ap.add_argument("--bitrate", type=int, default=1000000)
|
||
ap.add_argument("--motor-id", type=int, default=2)
|
||
ap.add_argument("--timeout", type=float, default=0.2, help="seconds")
|
||
ap.add_argument("--repeat", type=int, default=3, help="query times")
|
||
args = ap.parse_args()
|
||
|
||
motor_id = args.motor_id
|
||
rx_arb = arb_id_single(motor_id)
|
||
|
||
bus = can.Bus(interface=args.interface, channel=args.channel, bitrate=args.bitrate)
|
||
|
||
# 只收这个电机的回包(降低噪声)
|
||
try:
|
||
bus.set_filters([{"can_id": rx_arb, "can_mask": 0x7FF, "extended": False}])
|
||
except Exception:
|
||
pass
|
||
|
||
try:
|
||
for i in range(args.repeat):
|
||
# 发送 0x92
|
||
bus.send(build_read_multiturn_angle(motor_id))
|
||
|
||
# 等回包
|
||
msg = bus.recv(timeout=args.timeout)
|
||
if msg is None:
|
||
print(f"[M{motor_id}] timeout: no reply")
|
||
continue
|
||
if msg.arbitration_id != rx_arb or len(msg.data) != 8:
|
||
print(f"[M{motor_id}] unexpected frame: arb=0x{msg.arbitration_id:X} data={list(msg.data)}")
|
||
continue
|
||
|
||
# 解析(按你贴的文档:int56 in DATA[1..7])
|
||
motor_angle_raw = parse_multiturn_angle_reply_int56(msg)
|
||
|
||
# 单位换算:0.01°/LSB
|
||
angle_deg = motor_angle_raw * 0.01
|
||
angle_rad = angle_deg * math.pi / 180.0
|
||
|
||
print(
|
||
f"[M{motor_id}] motorAngle_raw={motor_angle_raw} "
|
||
f"angle_deg={angle_deg:+.4f}° angle_rad={angle_rad:+.6f} rad"
|
||
)
|
||
|
||
finally:
|
||
bus.shutdown()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|