emm-stepper 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emm_stepper/__init__.py +38 -0
- emm_stepper/commands.py +1284 -0
- emm_stepper/configs.py +476 -0
- emm_stepper/device.py +900 -0
- emm_stepper/parameters.py +590 -0
- emm_stepper-1.0.0.dist-info/METADATA +1240 -0
- emm_stepper-1.0.0.dist-info/RECORD +10 -0
- emm_stepper-1.0.0.dist-info/WHEEL +5 -0
- emm_stepper-1.0.0.dist-info/licenses/LICENSE +21 -0
- emm_stepper-1.0.0.dist-info/top_level.txt +1 -0
emm_stepper/commands.py
ADDED
|
@@ -0,0 +1,1284 @@
|
|
|
1
|
+
"""Emm固件步进电机命令类.
|
|
2
|
+
|
|
3
|
+
基于ZDT_X42S第二代闭环步进电机用户手册V1.0.3_251224。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from time import sleep, time
|
|
9
|
+
from typing import Optional, TypeVar, Generic, Any
|
|
10
|
+
|
|
11
|
+
from .configs import (
|
|
12
|
+
Code,
|
|
13
|
+
Protocol,
|
|
14
|
+
StatusCode,
|
|
15
|
+
ChecksumMode,
|
|
16
|
+
Address,
|
|
17
|
+
SyncFlag,
|
|
18
|
+
StoreFlag,
|
|
19
|
+
Direction,
|
|
20
|
+
EnableFlag,
|
|
21
|
+
HomingMode,
|
|
22
|
+
MotionMode,
|
|
23
|
+
add_checksum,
|
|
24
|
+
calculate_checksum,
|
|
25
|
+
SystemConstants,
|
|
26
|
+
)
|
|
27
|
+
from .parameters import (
|
|
28
|
+
DeviceParams,
|
|
29
|
+
JogParams,
|
|
30
|
+
PositionParams,
|
|
31
|
+
HomingParams,
|
|
32
|
+
VersionParams,
|
|
33
|
+
MotorRHParams,
|
|
34
|
+
PIDParams,
|
|
35
|
+
HomingStatus,
|
|
36
|
+
MotorStatus,
|
|
37
|
+
SystemStatusParams,
|
|
38
|
+
ConfigParams,
|
|
39
|
+
ProtectionThreshold,
|
|
40
|
+
AutoRunParams,
|
|
41
|
+
to_int,
|
|
42
|
+
to_signed_int,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
T = TypeVar('T')
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CommandError(Exception):
|
|
51
|
+
"""命令执行错误."""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Command(ABC, Generic[T]):
|
|
56
|
+
"""命令基类."""
|
|
57
|
+
|
|
58
|
+
_code: Code
|
|
59
|
+
_protocol: Optional[Protocol] = None
|
|
60
|
+
_response_length: int = 4 # 默认: 地址 + 功能码 + 状态 + 校验
|
|
61
|
+
|
|
62
|
+
def __init__(self, device: DeviceParams):
|
|
63
|
+
"""初始化命令.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
device: 设备参数
|
|
67
|
+
"""
|
|
68
|
+
self._timestamp = time()
|
|
69
|
+
self._response: Optional[bytes] = None
|
|
70
|
+
self._data: Optional[T] = None
|
|
71
|
+
self._status: StatusCode = StatusCode.FORMAT_ERROR
|
|
72
|
+
|
|
73
|
+
self.device = device
|
|
74
|
+
self.address = device.address
|
|
75
|
+
self.checksum_mode = device.checksum_mode
|
|
76
|
+
self.delay = device.delay
|
|
77
|
+
self.serial = device.serial_connection
|
|
78
|
+
|
|
79
|
+
# 构建并执行命令
|
|
80
|
+
self._command = self._build_command()
|
|
81
|
+
self._execute()
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def _build_command_body(self) -> bytes:
|
|
85
|
+
"""构建命令体(不含校验码)."""
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def _parse_response(self, data: bytes) -> T:
|
|
90
|
+
"""解析响应数据."""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def _build_command(self) -> bytes:
|
|
94
|
+
"""构建完整命令(含校验码)."""
|
|
95
|
+
body = self._build_command_body()
|
|
96
|
+
return add_checksum(body, self.checksum_mode)
|
|
97
|
+
|
|
98
|
+
def _execute(self) -> None:
|
|
99
|
+
"""执行命令."""
|
|
100
|
+
tries = 0
|
|
101
|
+
while tries < SystemConstants.MAX_RETRIES:
|
|
102
|
+
try:
|
|
103
|
+
# 清空缓冲区
|
|
104
|
+
self.serial.reset_input_buffer()
|
|
105
|
+
self.serial.reset_output_buffer()
|
|
106
|
+
|
|
107
|
+
# 发送命令
|
|
108
|
+
logger.debug(f"发送命令: {self._command.hex()}")
|
|
109
|
+
self.serial.write(self._command)
|
|
110
|
+
|
|
111
|
+
# 读取响应
|
|
112
|
+
response = self._read_response()
|
|
113
|
+
if response:
|
|
114
|
+
self._response = response
|
|
115
|
+
self._status = StatusCode.SUCCESS
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning(f"命令执行失败 (尝试 {tries + 1}): {e}")
|
|
120
|
+
tries += 1
|
|
121
|
+
|
|
122
|
+
if self.delay:
|
|
123
|
+
sleep(self.delay)
|
|
124
|
+
|
|
125
|
+
if tries >= SystemConstants.MAX_RETRIES:
|
|
126
|
+
logger.error("命令执行失败: 超过最大重试次数")
|
|
127
|
+
|
|
128
|
+
def _read_response(self) -> Optional[bytes]:
|
|
129
|
+
"""读取响应."""
|
|
130
|
+
# 读取地址
|
|
131
|
+
addr = self.serial.read(1)
|
|
132
|
+
if not addr:
|
|
133
|
+
raise CommandError("未收到响应")
|
|
134
|
+
|
|
135
|
+
# 验证地址
|
|
136
|
+
expected_addr = 1 if self.address == Address.BROADCAST else self.address
|
|
137
|
+
if addr[0] != expected_addr:
|
|
138
|
+
raise CommandError(f"地址不匹配: 期望 {expected_addr}, 收到 {addr[0]}")
|
|
139
|
+
|
|
140
|
+
# 读取功能码
|
|
141
|
+
code = self.serial.read(1)
|
|
142
|
+
if not code:
|
|
143
|
+
raise CommandError("未收到功能码")
|
|
144
|
+
|
|
145
|
+
logger.debug(f"收到功能码: 0x{code[0]:02X}")
|
|
146
|
+
|
|
147
|
+
# 读取数据
|
|
148
|
+
data_length = self._response_length - 3 # 减去地址、功能码、校验码
|
|
149
|
+
data = self.serial.read(data_length) if data_length > 0 else b''
|
|
150
|
+
|
|
151
|
+
# 读取校验码
|
|
152
|
+
checksum = self.serial.read(1)
|
|
153
|
+
if not checksum:
|
|
154
|
+
raise CommandError("未收到校验码")
|
|
155
|
+
|
|
156
|
+
# 验证校验码
|
|
157
|
+
response_body = addr + code + data
|
|
158
|
+
expected_checksum = calculate_checksum(response_body, self.checksum_mode)
|
|
159
|
+
if checksum[0] != expected_checksum:
|
|
160
|
+
raise CommandError(f"校验码不匹配: 期望 0x{expected_checksum:02X}, 收到 0x{checksum[0]:02X}")
|
|
161
|
+
|
|
162
|
+
# 解析数据
|
|
163
|
+
if data:
|
|
164
|
+
self._data = self._parse_response(data)
|
|
165
|
+
|
|
166
|
+
return response_body + checksum
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def response(self) -> Optional[bytes]:
|
|
170
|
+
"""返回原始响应."""
|
|
171
|
+
return self._response
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def data(self) -> Optional[T]:
|
|
175
|
+
"""返回解析后的数据."""
|
|
176
|
+
return self._data
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def is_success(self) -> bool:
|
|
180
|
+
"""命令是否成功."""
|
|
181
|
+
return self._status == StatusCode.SUCCESS
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def status(self) -> str:
|
|
185
|
+
"""返回状态字符串."""
|
|
186
|
+
return self._status.name
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class SimpleCommand(Command[bool]):
|
|
190
|
+
"""简单命令(只返回成功/失败)."""
|
|
191
|
+
|
|
192
|
+
def _parse_response(self, data: bytes) -> bool:
|
|
193
|
+
"""解析响应."""
|
|
194
|
+
if data[0] == StatusCode.SUCCESS:
|
|
195
|
+
return True
|
|
196
|
+
elif data[0] == StatusCode.PARAM_ERROR:
|
|
197
|
+
logger.warning("命令参数错误")
|
|
198
|
+
return False
|
|
199
|
+
elif data[0] == StatusCode.FORMAT_ERROR:
|
|
200
|
+
logger.warning("命令格式错误")
|
|
201
|
+
return False
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class ReadCommand(Command[T]):
|
|
206
|
+
"""读取命令基类."""
|
|
207
|
+
|
|
208
|
+
def _build_command_body(self) -> bytes:
|
|
209
|
+
"""构建命令体."""
|
|
210
|
+
return bytes([self.address, self._code])
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ==================== 触发动作命令 ====================
|
|
214
|
+
|
|
215
|
+
class CalibrateEncoder(SimpleCommand):
|
|
216
|
+
"""触发编码器校准.
|
|
217
|
+
|
|
218
|
+
对应命令: 5.2.1 触发编码器校准
|
|
219
|
+
发送: 01 06 45 6B
|
|
220
|
+
返回: 01 06 02 6B
|
|
221
|
+
"""
|
|
222
|
+
_code = Code.CAL_ENCODER
|
|
223
|
+
_protocol = Protocol.CAL_ENCODER
|
|
224
|
+
|
|
225
|
+
def _build_command_body(self) -> bytes:
|
|
226
|
+
return bytes([self.address, self._code, self._protocol])
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class Restart(SimpleCommand):
|
|
230
|
+
"""重启电机.
|
|
231
|
+
|
|
232
|
+
对应命令: 5.2.2 重启电机(X42S/Y42)
|
|
233
|
+
发送: 01 08 97 6B
|
|
234
|
+
返回: 01 08 02 6B
|
|
235
|
+
"""
|
|
236
|
+
_code = Code.RESTART
|
|
237
|
+
_protocol = Protocol.RESTART
|
|
238
|
+
|
|
239
|
+
def _build_command_body(self) -> bytes:
|
|
240
|
+
return bytes([self.address, self._code, self._protocol])
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class ZeroPosition(SimpleCommand):
|
|
244
|
+
"""将当前位置角度清零.
|
|
245
|
+
|
|
246
|
+
对应命令: 5.2.3 将当前位置角度清零
|
|
247
|
+
发送: 01 0A 6D 6B
|
|
248
|
+
返回: 01 0A 02 6B
|
|
249
|
+
"""
|
|
250
|
+
_code = Code.ZERO_POSITION
|
|
251
|
+
_protocol = Protocol.ZERO_POSITION
|
|
252
|
+
|
|
253
|
+
def _build_command_body(self) -> bytes:
|
|
254
|
+
return bytes([self.address, self._code, self._protocol])
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class ClearProtection(SimpleCommand):
|
|
258
|
+
"""解除堵转/过热/过流保护.
|
|
259
|
+
|
|
260
|
+
对应命令: 5.2.4 解除堵转/过热/过流保护
|
|
261
|
+
发送: 01 0E 52 6B
|
|
262
|
+
返回: 01 0E 02 6B
|
|
263
|
+
"""
|
|
264
|
+
_code = Code.CLEAR_PROTECTION
|
|
265
|
+
_protocol = Protocol.CLEAR_PROTECTION
|
|
266
|
+
|
|
267
|
+
def _build_command_body(self) -> bytes:
|
|
268
|
+
return bytes([self.address, self._code, self._protocol])
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class FactoryReset(SimpleCommand):
|
|
272
|
+
"""恢复出厂设置.
|
|
273
|
+
|
|
274
|
+
对应命令: 5.2.5 恢复出厂设置
|
|
275
|
+
发送: 01 0F 5F 6B
|
|
276
|
+
返回: 01 0F 02 6B
|
|
277
|
+
"""
|
|
278
|
+
_code = Code.FACTORY_RESET
|
|
279
|
+
_protocol = Protocol.FACTORY_RESET
|
|
280
|
+
|
|
281
|
+
def _build_command_body(self) -> bytes:
|
|
282
|
+
return bytes([self.address, self._code, self._protocol])
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ==================== 运动控制命令 ====================
|
|
286
|
+
|
|
287
|
+
class Enable(SimpleCommand):
|
|
288
|
+
"""电机使能控制.
|
|
289
|
+
|
|
290
|
+
对应命令: 5.3.2 电机使能控制
|
|
291
|
+
发送: 01 F3 AB 01 00 6B (使能)
|
|
292
|
+
返回: 01 F3 02 6B
|
|
293
|
+
"""
|
|
294
|
+
_code = Code.ENABLE
|
|
295
|
+
_protocol = Protocol.ENABLE
|
|
296
|
+
|
|
297
|
+
def __init__(self, device: DeviceParams, enable: bool = True,
|
|
298
|
+
sync_flag: SyncFlag = SyncFlag.IMMEDIATE):
|
|
299
|
+
self.enable = enable
|
|
300
|
+
self.sync_flag = sync_flag
|
|
301
|
+
super().__init__(device)
|
|
302
|
+
|
|
303
|
+
def _build_command_body(self) -> bytes:
|
|
304
|
+
return bytes([
|
|
305
|
+
self.address,
|
|
306
|
+
self._code,
|
|
307
|
+
self._protocol,
|
|
308
|
+
EnableFlag.ENABLE if self.enable else EnableFlag.DISABLE,
|
|
309
|
+
self.sync_flag,
|
|
310
|
+
])
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class Disable(Enable):
|
|
314
|
+
"""电机失能(松轴)."""
|
|
315
|
+
|
|
316
|
+
def __init__(self, device: DeviceParams, sync_flag: SyncFlag = SyncFlag.IMMEDIATE):
|
|
317
|
+
super().__init__(device, enable=False, sync_flag=sync_flag)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class Jog(SimpleCommand):
|
|
321
|
+
"""速度模式控制 (Emm固件).
|
|
322
|
+
|
|
323
|
+
对应命令: 5.3.7 速度模式控制(Emm)
|
|
324
|
+
发送: 01 F6 01 05DC 0A 00 6B
|
|
325
|
+
返回: 01 F6 02 6B
|
|
326
|
+
"""
|
|
327
|
+
_code = Code.JOG
|
|
328
|
+
|
|
329
|
+
def __init__(self, device: DeviceParams, params: Optional[JogParams] = None,
|
|
330
|
+
direction: Direction = Direction.CW, speed: int = 100,
|
|
331
|
+
acceleration: int = 10, sync_flag: SyncFlag = SyncFlag.IMMEDIATE):
|
|
332
|
+
if params:
|
|
333
|
+
self.params = params
|
|
334
|
+
else:
|
|
335
|
+
self.params = JogParams(
|
|
336
|
+
direction=direction,
|
|
337
|
+
speed=speed,
|
|
338
|
+
acceleration=acceleration,
|
|
339
|
+
sync_flag=sync_flag,
|
|
340
|
+
)
|
|
341
|
+
super().__init__(device)
|
|
342
|
+
|
|
343
|
+
def _build_command_body(self) -> bytes:
|
|
344
|
+
return bytes([self.address, self._code]) + self.params.bytes
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class Position(SimpleCommand):
|
|
348
|
+
"""位置模式控制 (Emm固件).
|
|
349
|
+
|
|
350
|
+
对应命令: 5.3.12 位置模式控制(Emm)
|
|
351
|
+
发送: 01 FD 01 05DC 00 00007D00 00 00 6B
|
|
352
|
+
返回: 01 FD 02 6B
|
|
353
|
+
"""
|
|
354
|
+
_code = Code.POSITION
|
|
355
|
+
|
|
356
|
+
def __init__(self, device: DeviceParams, params: Optional[PositionParams] = None,
|
|
357
|
+
direction: Direction = Direction.CW, speed: int = 100,
|
|
358
|
+
acceleration: int = 10, pulse_count: int = 3200,
|
|
359
|
+
motion_mode: MotionMode = MotionMode.RELATIVE_LAST,
|
|
360
|
+
sync_flag: SyncFlag = SyncFlag.IMMEDIATE):
|
|
361
|
+
if params:
|
|
362
|
+
self.params = params
|
|
363
|
+
else:
|
|
364
|
+
self.params = PositionParams(
|
|
365
|
+
direction=direction,
|
|
366
|
+
speed=speed,
|
|
367
|
+
acceleration=acceleration,
|
|
368
|
+
pulse_count=pulse_count,
|
|
369
|
+
motion_mode=motion_mode,
|
|
370
|
+
sync_flag=sync_flag,
|
|
371
|
+
)
|
|
372
|
+
super().__init__(device)
|
|
373
|
+
|
|
374
|
+
def _build_command_body(self) -> bytes:
|
|
375
|
+
return bytes([self.address, self._code]) + self.params.bytes
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class EStop(SimpleCommand):
|
|
379
|
+
"""立即停止.
|
|
380
|
+
|
|
381
|
+
对应命令: 5.3.13 立即停止
|
|
382
|
+
发送: 01 FE 98 00 6B
|
|
383
|
+
返回: 01 FE 02 6B
|
|
384
|
+
"""
|
|
385
|
+
_code = Code.ESTOP
|
|
386
|
+
_protocol = Protocol.ESTOP
|
|
387
|
+
|
|
388
|
+
def __init__(self, device: DeviceParams, sync_flag: SyncFlag = SyncFlag.IMMEDIATE):
|
|
389
|
+
self.sync_flag = sync_flag
|
|
390
|
+
super().__init__(device)
|
|
391
|
+
|
|
392
|
+
def _build_command_body(self) -> bytes:
|
|
393
|
+
return bytes([self.address, self._code, self._protocol, self.sync_flag])
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class SyncMove(SimpleCommand):
|
|
397
|
+
"""触发多机同步运动.
|
|
398
|
+
|
|
399
|
+
对应命令: 5.3.14 触发多机同步运动
|
|
400
|
+
发送: 00 FF 66 6B
|
|
401
|
+
返回: 01 FF 02 6B
|
|
402
|
+
"""
|
|
403
|
+
_code = Code.SYNC_MOVE
|
|
404
|
+
_protocol = Protocol.SYNC_MOVE
|
|
405
|
+
|
|
406
|
+
def _build_command_body(self) -> bytes:
|
|
407
|
+
# 使用广播地址
|
|
408
|
+
return bytes([Address.BROADCAST, self._code, self._protocol])
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# ==================== 原点回零命令 ====================
|
|
412
|
+
|
|
413
|
+
class SetHomeZero(SimpleCommand):
|
|
414
|
+
"""设置单圈回零的零点位置.
|
|
415
|
+
|
|
416
|
+
对应命令: 5.4.1 设置单圈回零的零点位置
|
|
417
|
+
发送: 01 93 88 01 6B
|
|
418
|
+
返回: 01 93 02 6B
|
|
419
|
+
"""
|
|
420
|
+
_code = Code.SET_HOME_ZERO
|
|
421
|
+
_protocol = Protocol.SET_HOME_ZERO
|
|
422
|
+
|
|
423
|
+
def __init__(self, device: DeviceParams, store: bool = True):
|
|
424
|
+
self.store = store
|
|
425
|
+
super().__init__(device)
|
|
426
|
+
|
|
427
|
+
def _build_command_body(self) -> bytes:
|
|
428
|
+
return bytes([
|
|
429
|
+
self.address,
|
|
430
|
+
self._code,
|
|
431
|
+
self._protocol,
|
|
432
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
433
|
+
])
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class Home(SimpleCommand):
|
|
437
|
+
"""触发回零.
|
|
438
|
+
|
|
439
|
+
对应命令: 5.4.2 触发回零
|
|
440
|
+
发送: 01 9A 00 00 6B
|
|
441
|
+
返回: 01 9A 02 6B
|
|
442
|
+
"""
|
|
443
|
+
_code = Code.HOME
|
|
444
|
+
|
|
445
|
+
def __init__(self, device: DeviceParams, mode: HomingMode = HomingMode.NEAREST,
|
|
446
|
+
sync_flag: SyncFlag = SyncFlag.IMMEDIATE):
|
|
447
|
+
self.mode = mode
|
|
448
|
+
self.sync_flag = sync_flag
|
|
449
|
+
super().__init__(device)
|
|
450
|
+
|
|
451
|
+
def _build_command_body(self) -> bytes:
|
|
452
|
+
return bytes([self.address, self._code, self.mode, self.sync_flag])
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class StopHome(SimpleCommand):
|
|
456
|
+
"""强制中断并退出回零操作.
|
|
457
|
+
|
|
458
|
+
对应命令: 5.4.3 强制中断并退出回零操作
|
|
459
|
+
发送: 01 9C 48 6B
|
|
460
|
+
返回: 01 9C 02 6B
|
|
461
|
+
"""
|
|
462
|
+
_code = Code.STOP_HOME
|
|
463
|
+
_protocol = Protocol.STOP_HOME
|
|
464
|
+
|
|
465
|
+
def _build_command_body(self) -> bytes:
|
|
466
|
+
return bytes([self.address, self._code, self._protocol])
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class GetHomingStatus(Command[HomingStatus]):
|
|
470
|
+
"""读取回零状态标志.
|
|
471
|
+
|
|
472
|
+
对应命令: 5.4.4 读取回零状态标志
|
|
473
|
+
发送: 01 3B 6B
|
|
474
|
+
返回: 01 3B 03 6B
|
|
475
|
+
"""
|
|
476
|
+
_code = Code.GET_HOME_STATUS
|
|
477
|
+
_response_length = 4
|
|
478
|
+
|
|
479
|
+
def _build_command_body(self) -> bytes:
|
|
480
|
+
return bytes([self.address, self._code])
|
|
481
|
+
|
|
482
|
+
def _parse_response(self, data: bytes) -> HomingStatus:
|
|
483
|
+
return HomingStatus.from_byte(data[0])
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class GetHomingParams(Command[HomingParams]):
|
|
487
|
+
"""读取回零参数.
|
|
488
|
+
|
|
489
|
+
对应命令: 5.4.5 读取回零参数
|
|
490
|
+
发送: 01 22 6B
|
|
491
|
+
返回: 01 22 + 15字节数据 + 6B
|
|
492
|
+
"""
|
|
493
|
+
_code = Code.GET_HOME_PARAM
|
|
494
|
+
_response_length = 18 # 地址 + 功能码 + 15字节数据 + 校验
|
|
495
|
+
|
|
496
|
+
def _build_command_body(self) -> bytes:
|
|
497
|
+
return bytes([self.address, self._code])
|
|
498
|
+
|
|
499
|
+
def _parse_response(self, data: bytes) -> HomingParams:
|
|
500
|
+
return HomingParams.from_bytes(data)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class SetHomingParams(SimpleCommand):
|
|
504
|
+
"""修改回零参数.
|
|
505
|
+
|
|
506
|
+
对应命令: 5.4.6 修改回零参数
|
|
507
|
+
"""
|
|
508
|
+
_code = Code.SET_HOME_PARAM
|
|
509
|
+
_protocol = Protocol.SET_HOME_PARAM
|
|
510
|
+
|
|
511
|
+
def __init__(self, device: DeviceParams, params: HomingParams, store: bool = True):
|
|
512
|
+
self.params = params
|
|
513
|
+
self.store = store
|
|
514
|
+
super().__init__(device)
|
|
515
|
+
|
|
516
|
+
def _build_command_body(self) -> bytes:
|
|
517
|
+
return bytes([
|
|
518
|
+
self.address,
|
|
519
|
+
self._code,
|
|
520
|
+
self._protocol,
|
|
521
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
522
|
+
]) + self.params.bytes
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# ==================== 读取系统参数命令 ====================
|
|
526
|
+
|
|
527
|
+
class GetVersion(Command[VersionParams]):
|
|
528
|
+
"""读取固件版本和硬件版本.
|
|
529
|
+
|
|
530
|
+
对应命令: 5.5.2 读取固件版本和硬件版本
|
|
531
|
+
发送: 01 1F 6B
|
|
532
|
+
返回: 01 1F + 4字节数据 + 6B
|
|
533
|
+
"""
|
|
534
|
+
_code = Code.GET_VERSION
|
|
535
|
+
_response_length = 7
|
|
536
|
+
|
|
537
|
+
def _build_command_body(self) -> bytes:
|
|
538
|
+
return bytes([self.address, self._code])
|
|
539
|
+
|
|
540
|
+
def _parse_response(self, data: bytes) -> VersionParams:
|
|
541
|
+
return VersionParams.from_bytes(data)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
class GetMotorRH(Command[MotorRHParams]):
|
|
545
|
+
"""读取相电阻和相电感.
|
|
546
|
+
|
|
547
|
+
对应命令: 5.5.3 读取相电阻和相电感
|
|
548
|
+
发送: 01 20 6B
|
|
549
|
+
返回: 01 20 + 4字节数据 + 6B
|
|
550
|
+
"""
|
|
551
|
+
_code = Code.GET_MOTOR_RH
|
|
552
|
+
_response_length = 7
|
|
553
|
+
|
|
554
|
+
def _build_command_body(self) -> bytes:
|
|
555
|
+
return bytes([self.address, self._code])
|
|
556
|
+
|
|
557
|
+
def _parse_response(self, data: bytes) -> MotorRHParams:
|
|
558
|
+
return MotorRHParams.from_bytes(data)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
class GetBusVoltage(Command[int]):
|
|
562
|
+
"""读取总线电压.
|
|
563
|
+
|
|
564
|
+
对应命令: 5.5.4 读取总线电压
|
|
565
|
+
发送: 01 24 6B
|
|
566
|
+
返回: 01 24 + 2字节数据 + 6B
|
|
567
|
+
"""
|
|
568
|
+
_code = Code.GET_BUS_VOLTAGE
|
|
569
|
+
_response_length = 5
|
|
570
|
+
|
|
571
|
+
def _build_command_body(self) -> bytes:
|
|
572
|
+
return bytes([self.address, self._code])
|
|
573
|
+
|
|
574
|
+
def _parse_response(self, data: bytes) -> int:
|
|
575
|
+
"""返回总线电压(mV)."""
|
|
576
|
+
return to_int(data)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
class GetBusCurrent(Command[int]):
|
|
580
|
+
"""读取总线电流.
|
|
581
|
+
|
|
582
|
+
对应命令: 5.5.5 读取总线电流(X42S/Y42)
|
|
583
|
+
发送: 01 26 6B
|
|
584
|
+
返回: 01 26 + 2字节数据 + 6B
|
|
585
|
+
"""
|
|
586
|
+
_code = Code.GET_BUS_CURRENT
|
|
587
|
+
_response_length = 5
|
|
588
|
+
|
|
589
|
+
def _build_command_body(self) -> bytes:
|
|
590
|
+
return bytes([self.address, self._code])
|
|
591
|
+
|
|
592
|
+
def _parse_response(self, data: bytes) -> int:
|
|
593
|
+
"""返回总线电流(mA)."""
|
|
594
|
+
return to_int(data)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
class GetPhaseCurrent(Command[int]):
|
|
598
|
+
"""读取相电流.
|
|
599
|
+
|
|
600
|
+
对应命令: 5.5.6 读取相电流
|
|
601
|
+
发送: 01 27 6B
|
|
602
|
+
返回: 01 27 + 2字节数据 + 6B
|
|
603
|
+
"""
|
|
604
|
+
_code = Code.GET_PHASE_CURRENT
|
|
605
|
+
_response_length = 5
|
|
606
|
+
|
|
607
|
+
def _build_command_body(self) -> bytes:
|
|
608
|
+
return bytes([self.address, self._code])
|
|
609
|
+
|
|
610
|
+
def _parse_response(self, data: bytes) -> int:
|
|
611
|
+
"""返回相电流(mA)."""
|
|
612
|
+
return to_int(data)
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
class GetEncoder(Command[int]):
|
|
616
|
+
"""读取线性化编码器值.
|
|
617
|
+
|
|
618
|
+
对应命令: 5.5.7 读取经过线性化校准后的编码器值
|
|
619
|
+
发送: 01 31 6B
|
|
620
|
+
返回: 01 31 + 2字节数据 + 6B
|
|
621
|
+
"""
|
|
622
|
+
_code = Code.GET_ENCODER
|
|
623
|
+
_response_length = 5
|
|
624
|
+
|
|
625
|
+
def _build_command_body(self) -> bytes:
|
|
626
|
+
return bytes([self.address, self._code])
|
|
627
|
+
|
|
628
|
+
def _parse_response(self, data: bytes) -> int:
|
|
629
|
+
"""返回编码器值(0-65535表示0-360度)."""
|
|
630
|
+
return to_int(data)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
class GetPulseCount(Command[int]):
|
|
634
|
+
"""读取输入脉冲数.
|
|
635
|
+
|
|
636
|
+
对应命令: 5.5.8 读取输入脉冲数
|
|
637
|
+
发送: 01 32 6B
|
|
638
|
+
返回: 01 32 + 5字节数据 + 6B
|
|
639
|
+
"""
|
|
640
|
+
_code = Code.GET_PULSE_COUNT
|
|
641
|
+
_response_length = 8
|
|
642
|
+
|
|
643
|
+
def _build_command_body(self) -> bytes:
|
|
644
|
+
return bytes([self.address, self._code])
|
|
645
|
+
|
|
646
|
+
def _parse_response(self, data: bytes) -> int:
|
|
647
|
+
"""返回输入脉冲数(带符号)."""
|
|
648
|
+
return to_signed_int(data)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
class GetTargetPosition(Command[float]):
|
|
652
|
+
"""读取电机目标位置.
|
|
653
|
+
|
|
654
|
+
对应命令: 5.5.9 读取电机目标位置
|
|
655
|
+
发送: 01 33 6B
|
|
656
|
+
返回: 01 33 + 5字节数据 + 6B
|
|
657
|
+
"""
|
|
658
|
+
_code = Code.GET_TARGET_POSITION
|
|
659
|
+
_response_length = 8
|
|
660
|
+
|
|
661
|
+
def _build_command_body(self) -> bytes:
|
|
662
|
+
return bytes([self.address, self._code])
|
|
663
|
+
|
|
664
|
+
def _parse_response(self, data: bytes) -> float:
|
|
665
|
+
"""返回目标位置角度(度).
|
|
666
|
+
|
|
667
|
+
Emm固件: 0-65535表示一圈0-360°
|
|
668
|
+
"""
|
|
669
|
+
value = to_signed_int(data)
|
|
670
|
+
return (value * 360) / 65536
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
class GetRealtimeSpeed(Command[int]):
|
|
674
|
+
"""读取电机实时转速.
|
|
675
|
+
|
|
676
|
+
对应命令: 5.5.11 读取电机实时转速
|
|
677
|
+
发送: 01 35 6B
|
|
678
|
+
返回: 01 35 + 3字节数据 + 6B
|
|
679
|
+
"""
|
|
680
|
+
_code = Code.GET_REALTIME_SPEED
|
|
681
|
+
_response_length = 6
|
|
682
|
+
|
|
683
|
+
def _build_command_body(self) -> bytes:
|
|
684
|
+
return bytes([self.address, self._code])
|
|
685
|
+
|
|
686
|
+
def _parse_response(self, data: bytes) -> int:
|
|
687
|
+
"""返回实时转速(RPM, 带符号)."""
|
|
688
|
+
sign = -1 if data[0] == 1 else 1
|
|
689
|
+
return sign * to_int(data[1:3])
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
class GetRealtimePosition(Command[float]):
|
|
693
|
+
"""读取电机实时位置.
|
|
694
|
+
|
|
695
|
+
对应命令: 5.5.13 读取电机实时位置
|
|
696
|
+
发送: 01 36 6B
|
|
697
|
+
返回: 01 36 + 5字节数据 + 6B
|
|
698
|
+
"""
|
|
699
|
+
_code = Code.GET_REALTIME_POSITION
|
|
700
|
+
_response_length = 8
|
|
701
|
+
|
|
702
|
+
def _build_command_body(self) -> bytes:
|
|
703
|
+
return bytes([self.address, self._code])
|
|
704
|
+
|
|
705
|
+
def _parse_response(self, data: bytes) -> float:
|
|
706
|
+
"""返回实时位置角度(度).
|
|
707
|
+
|
|
708
|
+
Emm固件: 0-65535表示一圈0-360°
|
|
709
|
+
"""
|
|
710
|
+
value = to_signed_int(data)
|
|
711
|
+
return (value * 360) / 65536
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
class GetPositionError(Command[float]):
|
|
715
|
+
"""读取电机位置误差.
|
|
716
|
+
|
|
717
|
+
对应命令: 5.5.14 读取电机位置误差
|
|
718
|
+
发送: 01 37 6B
|
|
719
|
+
返回: 01 37 + 5字节数据 + 6B
|
|
720
|
+
"""
|
|
721
|
+
_code = Code.GET_POSITION_ERROR
|
|
722
|
+
_response_length = 8
|
|
723
|
+
|
|
724
|
+
def _build_command_body(self) -> bytes:
|
|
725
|
+
return bytes([self.address, self._code])
|
|
726
|
+
|
|
727
|
+
def _parse_response(self, data: bytes) -> float:
|
|
728
|
+
"""返回位置误差角度(度).
|
|
729
|
+
|
|
730
|
+
Emm固件: 0-65535表示一圈0-360°
|
|
731
|
+
"""
|
|
732
|
+
value = to_signed_int(data)
|
|
733
|
+
return (value * 360) / 65536
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
class GetTemperature(Command[int]):
|
|
737
|
+
"""读取驱动温度.
|
|
738
|
+
|
|
739
|
+
对应命令: 5.5.12 读取驱动温度(X42S/Y42)
|
|
740
|
+
发送: 01 39 6B
|
|
741
|
+
返回: 01 39 + 2字节数据 + 6B
|
|
742
|
+
"""
|
|
743
|
+
_code = Code.GET_TEMPERATURE
|
|
744
|
+
_response_length = 5
|
|
745
|
+
|
|
746
|
+
def _build_command_body(self) -> bytes:
|
|
747
|
+
return bytes([self.address, self._code])
|
|
748
|
+
|
|
749
|
+
def _parse_response(self, data: bytes) -> int:
|
|
750
|
+
"""返回温度(°C, 带符号)."""
|
|
751
|
+
sign = -1 if data[0] == 0 else 1 # 00=负, 01=正
|
|
752
|
+
return sign * data[1]
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
class GetMotorStatus(Command[MotorStatus]):
|
|
756
|
+
"""读取电机状态标志.
|
|
757
|
+
|
|
758
|
+
对应命令: 5.5.15 读取电机状态标志
|
|
759
|
+
发送: 01 3A 6B
|
|
760
|
+
返回: 01 3A + 1字节数据 + 6B
|
|
761
|
+
"""
|
|
762
|
+
_code = Code.GET_MOTOR_STATUS
|
|
763
|
+
_response_length = 4
|
|
764
|
+
|
|
765
|
+
def _build_command_body(self) -> bytes:
|
|
766
|
+
return bytes([self.address, self._code])
|
|
767
|
+
|
|
768
|
+
def _parse_response(self, data: bytes) -> MotorStatus:
|
|
769
|
+
return MotorStatus.from_byte(data[0])
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
class GetPID(Command[PIDParams]):
|
|
773
|
+
"""读取PID参数 (Emm固件).
|
|
774
|
+
|
|
775
|
+
对应命令: 5.6.16 读取PID参数(Emm)
|
|
776
|
+
发送: 01 21 6B
|
|
777
|
+
返回: 01 21 + 12字节数据 + 6B
|
|
778
|
+
"""
|
|
779
|
+
_code = Code.GET_PID
|
|
780
|
+
_response_length = 15
|
|
781
|
+
|
|
782
|
+
def _build_command_body(self) -> bytes:
|
|
783
|
+
return bytes([self.address, self._code])
|
|
784
|
+
|
|
785
|
+
def _parse_response(self, data: bytes) -> PIDParams:
|
|
786
|
+
return PIDParams.from_bytes(data)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
class DynamicLengthCommand(Command[T]):
|
|
790
|
+
"""动态长度响应命令基类.
|
|
791
|
+
|
|
792
|
+
用于响应长度在响应数据中指定的命令(如读取配置参数、系统状态等)。
|
|
793
|
+
|
|
794
|
+
根据说明书 5.8.5 和 5.8.2,Emm固件返回格式:
|
|
795
|
+
- 字节1: 地址
|
|
796
|
+
- 字节2: 功能码 (0x42 或 0x43)
|
|
797
|
+
- 字节3: 字节数 (整个响应的总字节数,包括地址到校验码)
|
|
798
|
+
- 字节4: 参数个数
|
|
799
|
+
- 字节5-N: 数据
|
|
800
|
+
- 字节N+1: 校验码
|
|
801
|
+
|
|
802
|
+
例如 Emm固件 get_config 返回:
|
|
803
|
+
- 字节数=0x21(33) 表示整个响应共33字节
|
|
804
|
+
- 数据长度 = 字节数 - 4 (减去地址、功能码、字节数、参数个数) - 1 (校验码)
|
|
805
|
+
- 即 33 - 5 = 28 字节数据
|
|
806
|
+
"""
|
|
807
|
+
|
|
808
|
+
def _read_response(self) -> Optional[bytes]:
|
|
809
|
+
"""读取动态长度响应."""
|
|
810
|
+
# 读取地址
|
|
811
|
+
addr = self.serial.read(1)
|
|
812
|
+
if not addr:
|
|
813
|
+
raise CommandError("未收到响应")
|
|
814
|
+
|
|
815
|
+
# 验证地址
|
|
816
|
+
expected_addr = 1 if self.address == Address.BROADCAST else self.address
|
|
817
|
+
if addr[0] != expected_addr:
|
|
818
|
+
raise CommandError(f"地址不匹配: 期望 {expected_addr}, 收到 {addr[0]}")
|
|
819
|
+
|
|
820
|
+
# 读取功能码
|
|
821
|
+
code = self.serial.read(1)
|
|
822
|
+
if not code:
|
|
823
|
+
raise CommandError("未收到功能码")
|
|
824
|
+
|
|
825
|
+
logger.debug(f"收到功能码: 0x{code[0]:02X}")
|
|
826
|
+
|
|
827
|
+
# 读取字节数(整个响应的总字节数)
|
|
828
|
+
byte_count = self.serial.read(1)
|
|
829
|
+
if not byte_count:
|
|
830
|
+
raise CommandError("未收到字节数")
|
|
831
|
+
|
|
832
|
+
total_response_length = byte_count[0]
|
|
833
|
+
logger.debug(f"响应总字节数: {total_response_length}")
|
|
834
|
+
|
|
835
|
+
# 读取参数个数
|
|
836
|
+
param_count = self.serial.read(1)
|
|
837
|
+
if not param_count:
|
|
838
|
+
raise CommandError("未收到参数个数")
|
|
839
|
+
|
|
840
|
+
logger.debug(f"参数个数: {param_count[0]}")
|
|
841
|
+
|
|
842
|
+
# 计算剩余数据长度
|
|
843
|
+
# 已读取: 地址(1) + 功能码(1) + 字节数(1) + 参数个数(1) = 4 字节
|
|
844
|
+
# 剩余: 数据 + 校验码 = 总长度 - 4
|
|
845
|
+
# 数据长度 = 总长度 - 4 - 1(校验码) = 总长度 - 5
|
|
846
|
+
data_length = total_response_length - 5
|
|
847
|
+
remaining_data = self.serial.read(data_length)
|
|
848
|
+
if len(remaining_data) < data_length:
|
|
849
|
+
raise CommandError(f"数据不完整: 期望 {data_length} 字节, 收到 {len(remaining_data)} 字节")
|
|
850
|
+
|
|
851
|
+
# 读取校验码
|
|
852
|
+
checksum = self.serial.read(1)
|
|
853
|
+
if not checksum:
|
|
854
|
+
raise CommandError("未收到校验码")
|
|
855
|
+
|
|
856
|
+
# 组合完整数据(包含字节数和参数个数)
|
|
857
|
+
data = byte_count + param_count + remaining_data
|
|
858
|
+
|
|
859
|
+
# 验证校验码
|
|
860
|
+
response_body = addr + code + data
|
|
861
|
+
expected_checksum = calculate_checksum(response_body, self.checksum_mode)
|
|
862
|
+
if checksum[0] != expected_checksum:
|
|
863
|
+
raise CommandError(f"校验码不匹配: 期望 0x{expected_checksum:02X}, 收到 0x{checksum[0]:02X}")
|
|
864
|
+
|
|
865
|
+
# 解析数据
|
|
866
|
+
if data:
|
|
867
|
+
self._data = self._parse_response(data)
|
|
868
|
+
|
|
869
|
+
return response_body + checksum
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
class GetConfig(DynamicLengthCommand[ConfigParams]):
|
|
873
|
+
"""读取驱动配置参数 (Emm固件).
|
|
874
|
+
|
|
875
|
+
对应命令: 5.8.5 读取驱动配置参数(Emm)
|
|
876
|
+
发送: 01 42 6C 6B
|
|
877
|
+
返回: 01 42 21 15 + 数据 + 6B
|
|
878
|
+
|
|
879
|
+
Emm固件返回: 字节数=0x21(33), 参数个数=0x15(21)
|
|
880
|
+
"""
|
|
881
|
+
_code = Code.GET_CONFIG
|
|
882
|
+
_protocol = Protocol.GET_CONFIG
|
|
883
|
+
|
|
884
|
+
def _build_command_body(self) -> bytes:
|
|
885
|
+
return bytes([self.address, self._code, self._protocol])
|
|
886
|
+
|
|
887
|
+
def _parse_response(self, data: bytes) -> ConfigParams:
|
|
888
|
+
# data[0] = 字节数, data[1] = 参数个数, data[2:] = 实际数据
|
|
889
|
+
return ConfigParams.from_bytes(data[2:])
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
class GetSystemStatus(DynamicLengthCommand[SystemStatusParams]):
|
|
893
|
+
"""读取系统状态参数 (Emm固件).
|
|
894
|
+
|
|
895
|
+
对应命令: 5.8.2 读取系统状态参数(Emm)
|
|
896
|
+
发送: 01 43 7A 6B
|
|
897
|
+
返回: 01 43 1F 09 + 数据 + 6B
|
|
898
|
+
|
|
899
|
+
Emm固件返回: 字节数=0x1F(31), 参数个数=0x09(9)
|
|
900
|
+
"""
|
|
901
|
+
_code = Code.GET_SYS_STATUS
|
|
902
|
+
_protocol = Protocol.GET_SYS_STATUS
|
|
903
|
+
|
|
904
|
+
def _build_command_body(self) -> bytes:
|
|
905
|
+
return bytes([self.address, self._code, self._protocol])
|
|
906
|
+
|
|
907
|
+
def _parse_response(self, data: bytes) -> SystemStatusParams:
|
|
908
|
+
# data[0] = 字节数, data[1] = 参数个数, data[2:] = 实际数据
|
|
909
|
+
return SystemStatusParams.from_bytes(data[2:])
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
# ==================== 设置命令 ====================
|
|
913
|
+
|
|
914
|
+
class SetID(SimpleCommand):
|
|
915
|
+
"""修改电机ID/地址.
|
|
916
|
+
|
|
917
|
+
对应命令: 5.6.1 修改电机ID/地址
|
|
918
|
+
发送: 01 AE 4B 01 02 6B
|
|
919
|
+
返回: 01 AE 02 6B
|
|
920
|
+
"""
|
|
921
|
+
_code = Code.SET_ID
|
|
922
|
+
_protocol = Protocol.SET_ID
|
|
923
|
+
|
|
924
|
+
def __init__(self, device: DeviceParams, new_id: int, store: bool = True):
|
|
925
|
+
if not 1 <= new_id <= 255:
|
|
926
|
+
raise ValueError("ID必须在 1-255 之间")
|
|
927
|
+
self.new_id = new_id
|
|
928
|
+
self.store = store
|
|
929
|
+
super().__init__(device)
|
|
930
|
+
|
|
931
|
+
def _build_command_body(self) -> bytes:
|
|
932
|
+
return bytes([
|
|
933
|
+
self.address,
|
|
934
|
+
self._code,
|
|
935
|
+
self._protocol,
|
|
936
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
937
|
+
self.new_id,
|
|
938
|
+
])
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
class SetMicrostep(SimpleCommand):
|
|
942
|
+
"""修改细分值.
|
|
943
|
+
|
|
944
|
+
对应命令: 5.6.2 修改细分值
|
|
945
|
+
发送: 01 84 8A 01 10 6B
|
|
946
|
+
返回: 01 84 02 6B
|
|
947
|
+
"""
|
|
948
|
+
_code = Code.SET_MICROSTEP
|
|
949
|
+
_protocol = Protocol.SET_MICROSTEP
|
|
950
|
+
|
|
951
|
+
def __init__(self, device: DeviceParams, microstep: int, store: bool = True):
|
|
952
|
+
if not 1 <= microstep <= 256:
|
|
953
|
+
raise ValueError("细分值必须在 1-256 之间")
|
|
954
|
+
self.microstep = microstep if microstep < 256 else 0 # 256用0表示
|
|
955
|
+
self.store = store
|
|
956
|
+
super().__init__(device)
|
|
957
|
+
|
|
958
|
+
def _build_command_body(self) -> bytes:
|
|
959
|
+
return bytes([
|
|
960
|
+
self.address,
|
|
961
|
+
self._code,
|
|
962
|
+
self._protocol,
|
|
963
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
964
|
+
self.microstep,
|
|
965
|
+
])
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
class SetLoopMode(SimpleCommand):
|
|
969
|
+
"""修改开环/闭环控制模式.
|
|
970
|
+
|
|
971
|
+
对应命令: 5.6.7 修改开环/闭环控制模式
|
|
972
|
+
发送: 01 46 A6 01 01 6B
|
|
973
|
+
返回: 01 46 02 6B
|
|
974
|
+
"""
|
|
975
|
+
_code = Code.SET_LOOP_MODE
|
|
976
|
+
_protocol = Protocol.SET_LOOP_MODE
|
|
977
|
+
|
|
978
|
+
def __init__(self, device: DeviceParams, closed_loop: bool = True, store: bool = True):
|
|
979
|
+
self.closed_loop = closed_loop
|
|
980
|
+
self.store = store
|
|
981
|
+
super().__init__(device)
|
|
982
|
+
|
|
983
|
+
def _build_command_body(self) -> bytes:
|
|
984
|
+
from .configs import ControlMode
|
|
985
|
+
return bytes([
|
|
986
|
+
self.address,
|
|
987
|
+
self._code,
|
|
988
|
+
self._protocol,
|
|
989
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
990
|
+
ControlMode.CLOSED_LOOP if self.closed_loop else ControlMode.OPEN_LOOP,
|
|
991
|
+
])
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
class SetOpenLoopCurrent(SimpleCommand):
|
|
995
|
+
"""修改开环模式工作电流.
|
|
996
|
+
|
|
997
|
+
对应命令: 5.6.12 修改开环模式工作电流
|
|
998
|
+
发送: 01 44 33 01 04B0 6B
|
|
999
|
+
返回: 01 44 02 6B
|
|
1000
|
+
"""
|
|
1001
|
+
_code = Code.SET_OPEN_LOOP_CURRENT
|
|
1002
|
+
_protocol = Protocol.SET_OPEN_LOOP_CURRENT
|
|
1003
|
+
|
|
1004
|
+
def __init__(self, device: DeviceParams, current_ma: int, store: bool = True):
|
|
1005
|
+
if not 0 <= current_ma <= 5000:
|
|
1006
|
+
raise ValueError("电流必须在 0-5000 mA 之间")
|
|
1007
|
+
self.current = current_ma
|
|
1008
|
+
self.store = store
|
|
1009
|
+
super().__init__(device)
|
|
1010
|
+
|
|
1011
|
+
def _build_command_body(self) -> bytes:
|
|
1012
|
+
return bytes([
|
|
1013
|
+
self.address,
|
|
1014
|
+
self._code,
|
|
1015
|
+
self._protocol,
|
|
1016
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1017
|
+
(self.current >> 8) & 0xFF,
|
|
1018
|
+
self.current & 0xFF,
|
|
1019
|
+
])
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
class SetClosedLoopCurrent(SimpleCommand):
|
|
1023
|
+
"""修改闭环模式最大电流.
|
|
1024
|
+
|
|
1025
|
+
对应命令: 5.6.13 修改闭环模式最大电流
|
|
1026
|
+
发送: 01 45 66 01 0BB8 6B
|
|
1027
|
+
返回: 01 45 02 6B
|
|
1028
|
+
"""
|
|
1029
|
+
_code = Code.SET_CLOSED_LOOP_CURRENT
|
|
1030
|
+
_protocol = Protocol.SET_CLOSED_LOOP_CURRENT
|
|
1031
|
+
|
|
1032
|
+
def __init__(self, device: DeviceParams, current_ma: int, store: bool = True):
|
|
1033
|
+
if not 0 <= current_ma <= 5000:
|
|
1034
|
+
raise ValueError("电流必须在 0-5000 mA 之间")
|
|
1035
|
+
self.current = current_ma
|
|
1036
|
+
self.store = store
|
|
1037
|
+
super().__init__(device)
|
|
1038
|
+
|
|
1039
|
+
def _build_command_body(self) -> bytes:
|
|
1040
|
+
return bytes([
|
|
1041
|
+
self.address,
|
|
1042
|
+
self._code,
|
|
1043
|
+
self._protocol,
|
|
1044
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1045
|
+
(self.current >> 8) & 0xFF,
|
|
1046
|
+
self.current & 0xFF,
|
|
1047
|
+
])
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
class SetPID(SimpleCommand):
|
|
1051
|
+
"""修改PID参数 (Emm固件).
|
|
1052
|
+
|
|
1053
|
+
对应命令: 5.6.17 修改PID参数(Emm)
|
|
1054
|
+
"""
|
|
1055
|
+
_code = Code.SET_PID
|
|
1056
|
+
_protocol = Protocol.SET_PID
|
|
1057
|
+
|
|
1058
|
+
def __init__(self, device: DeviceParams, params: PIDParams, store: bool = True):
|
|
1059
|
+
self.params = params
|
|
1060
|
+
self.store = store
|
|
1061
|
+
super().__init__(device)
|
|
1062
|
+
|
|
1063
|
+
def _build_command_body(self) -> bytes:
|
|
1064
|
+
return bytes([
|
|
1065
|
+
self.address,
|
|
1066
|
+
self._code,
|
|
1067
|
+
self._protocol,
|
|
1068
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1069
|
+
]) + self.params.bytes
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
class SetMotorDirection(SimpleCommand):
|
|
1073
|
+
"""修改电机运动正方向.
|
|
1074
|
+
|
|
1075
|
+
对应命令: 5.6.8 修改电机运动正方向
|
|
1076
|
+
发送: 01 D4 60 01 00 6B
|
|
1077
|
+
返回: 01 D4 02 6B
|
|
1078
|
+
"""
|
|
1079
|
+
_code = Code.SET_MOTOR_DIRECTION
|
|
1080
|
+
_protocol = Protocol.SET_MOTOR_DIRECTION
|
|
1081
|
+
|
|
1082
|
+
def __init__(self, device: DeviceParams, direction: Direction = Direction.CW, store: bool = True):
|
|
1083
|
+
self.direction = direction
|
|
1084
|
+
self.store = store
|
|
1085
|
+
super().__init__(device)
|
|
1086
|
+
|
|
1087
|
+
def _build_command_body(self) -> bytes:
|
|
1088
|
+
return bytes([
|
|
1089
|
+
self.address,
|
|
1090
|
+
self._code,
|
|
1091
|
+
self._protocol,
|
|
1092
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1093
|
+
self.direction,
|
|
1094
|
+
])
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
class SetPositionWindow(SimpleCommand):
|
|
1098
|
+
"""修改位置到达窗口.
|
|
1099
|
+
|
|
1100
|
+
对应命令: 5.6.21 修改位置到达窗口(X42S/Y42)
|
|
1101
|
+
发送: 01 D1 07 01 0008 6B
|
|
1102
|
+
返回: 01 D1 02 6B
|
|
1103
|
+
"""
|
|
1104
|
+
_code = Code.SET_POSITION_WINDOW
|
|
1105
|
+
_protocol = Protocol.SET_POSITION_WINDOW
|
|
1106
|
+
|
|
1107
|
+
def __init__(self, device: DeviceParams, window_deg: float = 0.8, store: bool = True):
|
|
1108
|
+
"""设置位置到达窗口.
|
|
1109
|
+
|
|
1110
|
+
Args:
|
|
1111
|
+
device: 设备参数
|
|
1112
|
+
window_deg: 位置到达窗口(度), 默认0.8度
|
|
1113
|
+
store: 是否存储
|
|
1114
|
+
"""
|
|
1115
|
+
self.window = int(window_deg * 10) # 内部缩小10倍处理
|
|
1116
|
+
self.store = store
|
|
1117
|
+
super().__init__(device)
|
|
1118
|
+
|
|
1119
|
+
def _build_command_body(self) -> bytes:
|
|
1120
|
+
return bytes([
|
|
1121
|
+
self.address,
|
|
1122
|
+
self._code,
|
|
1123
|
+
self._protocol,
|
|
1124
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1125
|
+
(self.window >> 8) & 0xFF,
|
|
1126
|
+
self.window & 0xFF,
|
|
1127
|
+
])
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
class SetHeartbeatTime(SimpleCommand):
|
|
1131
|
+
"""修改心跳保护功能时间.
|
|
1132
|
+
|
|
1133
|
+
对应命令: 5.6.25 修改心跳保护功能时间(X42S/Y42)
|
|
1134
|
+
发送: 01 68 38 01 00001388 6B
|
|
1135
|
+
返回: 01 68 02 6B
|
|
1136
|
+
"""
|
|
1137
|
+
_code = Code.SET_HEARTBEAT_TIME
|
|
1138
|
+
_protocol = Protocol.SET_HEARTBEAT_TIME
|
|
1139
|
+
|
|
1140
|
+
def __init__(self, device: DeviceParams, time_ms: int = 0, store: bool = True):
|
|
1141
|
+
"""设置心跳保护时间.
|
|
1142
|
+
|
|
1143
|
+
Args:
|
|
1144
|
+
device: 设备参数
|
|
1145
|
+
time_ms: 心跳保护时间(毫秒), 0表示关闭
|
|
1146
|
+
store: 是否存储
|
|
1147
|
+
"""
|
|
1148
|
+
self.time_ms = time_ms
|
|
1149
|
+
self.store = store
|
|
1150
|
+
super().__init__(device)
|
|
1151
|
+
|
|
1152
|
+
def _build_command_body(self) -> bytes:
|
|
1153
|
+
return bytes([
|
|
1154
|
+
self.address,
|
|
1155
|
+
self._code,
|
|
1156
|
+
self._protocol,
|
|
1157
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1158
|
+
(self.time_ms >> 24) & 0xFF,
|
|
1159
|
+
(self.time_ms >> 16) & 0xFF,
|
|
1160
|
+
(self.time_ms >> 8) & 0xFF,
|
|
1161
|
+
self.time_ms & 0xFF,
|
|
1162
|
+
])
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
class SetAutoRun(SimpleCommand):
|
|
1166
|
+
"""存储一组速度参数,上电自动运行 (Emm固件).
|
|
1167
|
+
|
|
1168
|
+
对应命令: 5.7.2 存储一组速度参数,上电自动运行(Emm)
|
|
1169
|
+
"""
|
|
1170
|
+
_code = Code.SET_AUTO_RUN
|
|
1171
|
+
_protocol = Protocol.SET_AUTO_RUN
|
|
1172
|
+
|
|
1173
|
+
def __init__(self, device: DeviceParams, params: AutoRunParams):
|
|
1174
|
+
self.params = params
|
|
1175
|
+
super().__init__(device)
|
|
1176
|
+
|
|
1177
|
+
def _build_command_body(self) -> bytes:
|
|
1178
|
+
return bytes([
|
|
1179
|
+
self.address,
|
|
1180
|
+
self._code,
|
|
1181
|
+
self._protocol,
|
|
1182
|
+
]) + self.params.bytes
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
class SetConfig(SimpleCommand):
|
|
1186
|
+
"""修改驱动配置参数 (Emm固件).
|
|
1187
|
+
|
|
1188
|
+
对应命令: 5.8.6 修改驱动配置参数(Emm)
|
|
1189
|
+
"""
|
|
1190
|
+
_code = Code.SET_CONFIG
|
|
1191
|
+
_protocol = Protocol.SET_CONFIG
|
|
1192
|
+
|
|
1193
|
+
def __init__(self, device: DeviceParams, params: ConfigParams, store: bool = True):
|
|
1194
|
+
self.params = params
|
|
1195
|
+
self.store = store
|
|
1196
|
+
super().__init__(device)
|
|
1197
|
+
|
|
1198
|
+
def _build_command_body(self) -> bytes:
|
|
1199
|
+
return bytes([
|
|
1200
|
+
self.address,
|
|
1201
|
+
self._code,
|
|
1202
|
+
self._protocol,
|
|
1203
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1204
|
+
]) + self.params.bytes
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
class SetScaleInput(SimpleCommand):
|
|
1208
|
+
"""修改命令速度值是否缩小10倍输入 (Emm固件).
|
|
1209
|
+
|
|
1210
|
+
对应命令: 5.6.11 修改命令速度值是否缩小10倍输入(Emm)
|
|
1211
|
+
发送: 01 4F 71 01 01 6B
|
|
1212
|
+
返回: 01 4F 02 6B
|
|
1213
|
+
"""
|
|
1214
|
+
_code = Code.SET_SCALE_INPUT
|
|
1215
|
+
_protocol = Protocol.SET_SCALE_INPUT
|
|
1216
|
+
|
|
1217
|
+
def __init__(self, device: DeviceParams, enable: bool = False, store: bool = True):
|
|
1218
|
+
"""设置速度值缩小10倍输入.
|
|
1219
|
+
|
|
1220
|
+
Args:
|
|
1221
|
+
device: 设备参数
|
|
1222
|
+
enable: 是否使能(使能后输入1RPM实际为0.1RPM)
|
|
1223
|
+
store: 是否存储
|
|
1224
|
+
"""
|
|
1225
|
+
self.enable = enable
|
|
1226
|
+
self.store = store
|
|
1227
|
+
super().__init__(device)
|
|
1228
|
+
|
|
1229
|
+
def _build_command_body(self) -> bytes:
|
|
1230
|
+
return bytes([
|
|
1231
|
+
self.address,
|
|
1232
|
+
self._code,
|
|
1233
|
+
self._protocol,
|
|
1234
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1235
|
+
1 if self.enable else 0,
|
|
1236
|
+
])
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
class SetLockButton(SimpleCommand):
|
|
1240
|
+
"""修改锁定按键功能.
|
|
1241
|
+
|
|
1242
|
+
对应命令: 5.6.9 修改锁定按键功能
|
|
1243
|
+
发送: 01 D0 B3 01 01 6B
|
|
1244
|
+
返回: 01 D0 02 6B
|
|
1245
|
+
"""
|
|
1246
|
+
_code = Code.SET_LOCK_BUTTON
|
|
1247
|
+
_protocol = Protocol.SET_LOCK_BUTTON
|
|
1248
|
+
|
|
1249
|
+
def __init__(self, device: DeviceParams, lock: bool = False, store: bool = True):
|
|
1250
|
+
self.lock = lock
|
|
1251
|
+
self.store = store
|
|
1252
|
+
super().__init__(device)
|
|
1253
|
+
|
|
1254
|
+
def _build_command_body(self) -> bytes:
|
|
1255
|
+
return bytes([
|
|
1256
|
+
self.address,
|
|
1257
|
+
self._code,
|
|
1258
|
+
self._protocol,
|
|
1259
|
+
StoreFlag.STORE if self.store else StoreFlag.NO_STORE,
|
|
1260
|
+
1 if self.lock else 0,
|
|
1261
|
+
])
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
class BroadcastGetID(Command[int]):
|
|
1265
|
+
"""广播读取ID地址.
|
|
1266
|
+
|
|
1267
|
+
对应命令: 5.6.30 广播读取ID地址(X42S/Y42)
|
|
1268
|
+
发送: 00 15 6B
|
|
1269
|
+
返回: 01 15 01 6B
|
|
1270
|
+
"""
|
|
1271
|
+
_code = Code.BROADCAST_GET_ID
|
|
1272
|
+
_response_length = 4
|
|
1273
|
+
|
|
1274
|
+
def __init__(self, device: DeviceParams):
|
|
1275
|
+
# 强制使用广播地址
|
|
1276
|
+
device.address = Address(Address.BROADCAST)
|
|
1277
|
+
super().__init__(device)
|
|
1278
|
+
|
|
1279
|
+
def _build_command_body(self) -> bytes:
|
|
1280
|
+
return bytes([Address.BROADCAST, self._code])
|
|
1281
|
+
|
|
1282
|
+
def _parse_response(self, data: bytes) -> int:
|
|
1283
|
+
"""返回电机ID."""
|
|
1284
|
+
return data[0]
|