moons-motor 0.1.3__py3-none-any.whl → 0.1.5__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.
- moons_motor/motor.py +145 -23
- {moons_motor-0.1.3.dist-info → moons_motor-0.1.5.dist-info}/METADATA +5 -5
- {moons_motor-0.1.3.dist-info → moons_motor-0.1.5.dist-info}/RECORD +6 -6
- {moons_motor-0.1.3.dist-info → moons_motor-0.1.5.dist-info}/WHEEL +0 -0
- {moons_motor-0.1.3.dist-info → moons_motor-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {moons_motor-0.1.3.dist-info → moons_motor-0.1.5.dist-info}/top_level.txt +0 -0
moons_motor/motor.py
CHANGED
@@ -13,6 +13,66 @@ import threading
|
|
13
13
|
|
14
14
|
from dataclasses import dataclass
|
15
15
|
|
16
|
+
import logging
|
17
|
+
|
18
|
+
|
19
|
+
class ColoredFormatter(logging.Formatter):
|
20
|
+
# Define ANSI escape codes for colors and reset
|
21
|
+
grey = "\x1b[38;21m"
|
22
|
+
yellow = "\x1b[33;21m"
|
23
|
+
red = "\x1b[31;21m"
|
24
|
+
bold_red = "\x1b[31;1m"
|
25
|
+
bold_yellow = "\x1b[33;1m"
|
26
|
+
bold_green = "\x1b[32;1m"
|
27
|
+
bold_blue = "\x1b[34;1m"
|
28
|
+
bold_cyan = "\x1b[36;1m"
|
29
|
+
bold_magenta = "\x1b[35;1m"
|
30
|
+
reset = "\x1b[0m"
|
31
|
+
|
32
|
+
# Define the base format string
|
33
|
+
format_time = "%(asctime)s"
|
34
|
+
format_header = " [%(levelname)s]"
|
35
|
+
format_base = " %(message)s"
|
36
|
+
|
37
|
+
# Map log levels to colored format strings
|
38
|
+
FORMATS = {
|
39
|
+
logging.DEBUG: format_time
|
40
|
+
+ bold_cyan
|
41
|
+
+ format_header
|
42
|
+
+ reset
|
43
|
+
+ format_base
|
44
|
+
+ reset,
|
45
|
+
logging.INFO: format_time
|
46
|
+
+ bold_green
|
47
|
+
+ format_header
|
48
|
+
+ reset
|
49
|
+
+ format_base
|
50
|
+
+ reset,
|
51
|
+
logging.WARNING: format_time
|
52
|
+
+ bold_yellow
|
53
|
+
+ format_header
|
54
|
+
+ reset
|
55
|
+
+ format_base
|
56
|
+
+ reset,
|
57
|
+
logging.ERROR: format_time
|
58
|
+
+ bold_red
|
59
|
+
+ format_header
|
60
|
+
+ reset
|
61
|
+
+ format_base
|
62
|
+
+ reset,
|
63
|
+
logging.CRITICAL: format_time
|
64
|
+
+ bold_magenta
|
65
|
+
+ format_header
|
66
|
+
+ reset
|
67
|
+
+ format_base
|
68
|
+
+ reset,
|
69
|
+
}
|
70
|
+
|
71
|
+
def format(self, record):
|
72
|
+
log_fmt = self.FORMATS.get(record.levelno)
|
73
|
+
formatter = logging.Formatter(log_fmt)
|
74
|
+
return formatter.format(record)
|
75
|
+
|
16
76
|
|
17
77
|
class StepperModules:
|
18
78
|
STM17S_3RN = "STM17S-3RN"
|
@@ -22,6 +82,9 @@ class StepperModules:
|
|
22
82
|
class StepperCommand:
|
23
83
|
JOG: str = "CJ" # Start jogging
|
24
84
|
JOG_SPEED: str = "JS" # Jogging speed (Need to set before start jogging)
|
85
|
+
JOG_ACCELERATION: str = (
|
86
|
+
"JA" # Jogging acceleration (Need to set before start jogging)
|
87
|
+
)
|
25
88
|
CHANGE_JOG_SPEED: str = "CS" # Change jogging speed while jogging
|
26
89
|
STOP_JOG: str = "SJ" # Stop jogging with deceleration
|
27
90
|
STOP: str = "ST" # Stop immediately (No deceleration)
|
@@ -52,6 +115,7 @@ class StepperCommand:
|
|
52
115
|
SET_RETURN_FORMAT_HEXADECIMAL: str = "IFH" # Set return format to hexadecimal
|
53
116
|
|
54
117
|
SET_TRANSMIT_DELAY: str = "TD" # Set transmit delay
|
118
|
+
REQUEST_STATUS: str = "RS" # Request status
|
55
119
|
|
56
120
|
|
57
121
|
class MoonsStepper(Subject):
|
@@ -89,6 +153,13 @@ class MoonsStepper(Subject):
|
|
89
153
|
"?",
|
90
154
|
"@",
|
91
155
|
]
|
156
|
+
# Configure logging
|
157
|
+
logger = logging.getLogger(__name__)
|
158
|
+
logger.setLevel(logging.INFO)
|
159
|
+
|
160
|
+
ch = logging.StreamHandler()
|
161
|
+
ch.setFormatter(ColoredFormatter())
|
162
|
+
logger.addHandler(ch)
|
92
163
|
|
93
164
|
def __init__(
|
94
165
|
self,
|
@@ -178,7 +249,7 @@ class MoonsStepper(Subject):
|
|
178
249
|
if self.only_simulate:
|
179
250
|
self.Opened = True
|
180
251
|
self.device = f"Simulate-{self.universe}"
|
181
|
-
|
252
|
+
MoonsStepper.logger.info(f"{self.device} connected")
|
182
253
|
if callback:
|
183
254
|
callback(self.device, self.Opened)
|
184
255
|
return
|
@@ -205,10 +276,10 @@ class MoonsStepper(Subject):
|
|
205
276
|
self.Opened = False
|
206
277
|
if self.ser.is_open:
|
207
278
|
self.Opened = True
|
208
|
-
|
279
|
+
MoonsStepper.logger.info(f"Device connected: {self.device}")
|
209
280
|
|
210
281
|
except Exception as e:
|
211
|
-
|
282
|
+
MoonsStepper.logger.error(f"Device error: {e} ")
|
212
283
|
self.Opened = False
|
213
284
|
|
214
285
|
ports = list(list_ports.comports())
|
@@ -221,7 +292,7 @@ class MoonsStepper(Subject):
|
|
221
292
|
m = re.match(
|
222
293
|
r"USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]*)", p.usb_info()
|
223
294
|
)
|
224
|
-
|
295
|
+
MoonsStepper.logger.info(p.usb_info())
|
225
296
|
if (
|
226
297
|
m
|
227
298
|
and m.group(1) == self.VID
|
@@ -229,8 +300,8 @@ class MoonsStepper(Subject):
|
|
229
300
|
# and m.group(3) == self.SERIAL_NUM
|
230
301
|
):
|
231
302
|
if m.group(3) == self.SERIAL_NUM or self.SERIAL_NUM == "":
|
232
|
-
|
233
|
-
f"
|
303
|
+
MoonsStepper.logger.info(
|
304
|
+
f"Device founded: {p.description} | VID: {m.group(1)} | PID: {m.group(2)} | SER: {m.group(3)}"
|
234
305
|
)
|
235
306
|
|
236
307
|
self.device = p.description
|
@@ -248,7 +319,7 @@ class MoonsStepper(Subject):
|
|
248
319
|
callback(self.device, self.Opened)
|
249
320
|
|
250
321
|
if not self.Opened:
|
251
|
-
|
322
|
+
MoonsStepper.logger.error(f"Device not found")
|
252
323
|
if callback:
|
253
324
|
callback(self.device, self.Opened)
|
254
325
|
|
@@ -263,7 +334,7 @@ class MoonsStepper(Subject):
|
|
263
334
|
self.listen = False
|
264
335
|
self.is_sending = False
|
265
336
|
self.Opened = False
|
266
|
-
|
337
|
+
MoonsStepper.logger.info(f"Simulate-{self.universe} disconnected")
|
267
338
|
return
|
268
339
|
if self.ser is not None and self.ser.is_open:
|
269
340
|
self.listen = False
|
@@ -271,7 +342,7 @@ class MoonsStepper(Subject):
|
|
271
342
|
self.Opened = False
|
272
343
|
self.ser.flush()
|
273
344
|
self.ser.close()
|
274
|
-
|
345
|
+
MoonsStepper.logger.info(f"Device disconnected: {self.device}")
|
275
346
|
|
276
347
|
def send(self, command, eol=b"\r"):
|
277
348
|
if (self.ser != None and self.ser.is_open) or self.only_simulate:
|
@@ -280,16 +351,16 @@ class MoonsStepper(Subject):
|
|
280
351
|
if self.ser is not None or not self.only_simulate:
|
281
352
|
self.ser.write(self.temp_cmd.encode("ascii"))
|
282
353
|
if self.is_log_message:
|
283
|
-
|
284
|
-
f"[bold green]Send to {self.device}:[/bold green] {self.temp_cmd}"
|
285
|
-
)
|
354
|
+
MoonsStepper.logger.debug(f"Send to {self.device}: {self.temp_cmd}")
|
286
355
|
super().notify_observers(f"{self.universe}-{self.temp_cmd}")
|
287
356
|
else:
|
288
|
-
|
357
|
+
MoonsStepper.logger.debug(
|
358
|
+
f"Target device is not opened. Command: {command}"
|
359
|
+
)
|
289
360
|
|
290
361
|
def send_command(self, address="", command="", value=None):
|
291
362
|
if command == "":
|
292
|
-
|
363
|
+
MoonsStepper.logger.warning("Command can't be empty")
|
293
364
|
return
|
294
365
|
if value is not None:
|
295
366
|
command = self.addressed_cmd(address, command + str(value))
|
@@ -326,13 +397,13 @@ class MoonsStepper(Subject):
|
|
326
397
|
|
327
398
|
def handle_recv(self, response):
|
328
399
|
if "*" in response:
|
329
|
-
|
400
|
+
MoonsStepper.logger.info(f"(o)buffered_ack")
|
330
401
|
elif "%" in response:
|
331
|
-
|
402
|
+
MoonsStepper.logger.info(f"(v)success_ack")
|
332
403
|
elif "?" in response:
|
333
|
-
|
404
|
+
MoonsStepper.logger.info(f"(x)fail_ack")
|
334
405
|
else:
|
335
|
-
|
406
|
+
MoonsStepper.logger.info(f"Received from {self.device}: ", response)
|
336
407
|
self.recvQueue.put_nowait(response)
|
337
408
|
|
338
409
|
if "=" in response:
|
@@ -357,6 +428,32 @@ class MoonsStepper(Subject):
|
|
357
428
|
# self.set_return_format_dexcimal(motor_address)
|
358
429
|
|
359
430
|
def home(self, motor_address="", speed=0.3, onComplete=None):
|
431
|
+
homing_complete = threading.Event() # Shared event to signal completion
|
432
|
+
|
433
|
+
def check_status(response):
|
434
|
+
result = MoonsStepper.process_response(response)
|
435
|
+
MoonsStepper.logger.info(f"Status check result: {result}")
|
436
|
+
if "H" not in result["value"]:
|
437
|
+
MoonsStepper.logger.info("Motor is homed.")
|
438
|
+
if onComplete: # Call the onComplete callback if provided
|
439
|
+
onComplete(result)
|
440
|
+
homing_complete.set() # Signal that homing is complete
|
441
|
+
else:
|
442
|
+
MoonsStepper.logger.info("Motor is not homed yet.")
|
443
|
+
|
444
|
+
def check_homing_complete():
|
445
|
+
while not homing_complete.is_set(): # Loop until homing is complete
|
446
|
+
self.get_status(
|
447
|
+
motor_address=motor_address,
|
448
|
+
command=StepperCommand.REQUEST_STATUS,
|
449
|
+
callback=check_status,
|
450
|
+
)
|
451
|
+
time.sleep(0.3)
|
452
|
+
|
453
|
+
home_thread = threading.Thread(
|
454
|
+
target=check_homing_complete,
|
455
|
+
daemon=True,
|
456
|
+
)
|
360
457
|
self.send_command(
|
361
458
|
address=motor_address, command=StepperCommand.VELOCITY, value=speed
|
362
459
|
)
|
@@ -364,15 +461,12 @@ class MoonsStepper(Subject):
|
|
364
461
|
address=motor_address, command=StepperCommand.HOME, value="3F"
|
365
462
|
)
|
366
463
|
self.send_command(
|
367
|
-
address=motor_address, command=StepperCommand.ENCODER_POSITION
|
464
|
+
address=motor_address, command=StepperCommand.ENCODER_POSITION, value=0
|
368
465
|
)
|
369
466
|
self.send_command(
|
370
467
|
address=motor_address, command=StepperCommand.SET_POSITION, value=0
|
371
468
|
)
|
372
|
-
|
373
|
-
self.get_status(
|
374
|
-
motor_address, StepperCommand.SET_POSITION, callback=onComplete
|
375
|
-
)
|
469
|
+
home_thread.start()
|
376
470
|
|
377
471
|
# endregion
|
378
472
|
def get_status(self, motor_address, command: StepperCommand, callback=None):
|
@@ -381,6 +475,33 @@ class MoonsStepper(Subject):
|
|
381
475
|
self.pending_callbacks.put_nowait(callback)
|
382
476
|
self.sendQueue.put_nowait(command)
|
383
477
|
|
478
|
+
def decode_status(status_code):
|
479
|
+
"""
|
480
|
+
Decode the status code from the motor.
|
481
|
+
"""
|
482
|
+
status = {
|
483
|
+
"A": "An Alarm code is present (use AL command to see code, AR command to clear code)",
|
484
|
+
"D": "Disabled (the drive is disabled)",
|
485
|
+
"E": "Drive Fault (drive must be reset by AR command to clear this fault)",
|
486
|
+
"F": "Motor moving",
|
487
|
+
"H": "Homing (SH in progress)",
|
488
|
+
"J": "Jogging (CJ in progress)",
|
489
|
+
"M": "Motion in progress (Feed & Jog Commands)",
|
490
|
+
"P": "In position",
|
491
|
+
"R": "Ready (Drive is enabled and ready)",
|
492
|
+
"S": "Stopping a motion (ST or SK command executing)",
|
493
|
+
"T": "Wait Time (WT command executing)",
|
494
|
+
"W": "Wait Input (WI command executing)",
|
495
|
+
}
|
496
|
+
status_string = ""
|
497
|
+
for char in status_code:
|
498
|
+
if char in status:
|
499
|
+
status_string += status[char]
|
500
|
+
status_string += "\n"
|
501
|
+
else:
|
502
|
+
status_string += f"Unknown status code: {char}"
|
503
|
+
return status_string
|
504
|
+
|
384
505
|
# endregion
|
385
506
|
|
386
507
|
# region utility functions
|
@@ -394,5 +515,6 @@ class MoonsStepper(Subject):
|
|
394
515
|
# SERIAL => 上次已知父系(尾巴+A) 或是事件分頁
|
395
516
|
# reg USB\s*VID:PID=(\w+):(\w+)\s*SER=([A-Za-z0-9]+)
|
396
517
|
|
518
|
+
|
397
519
|
# serial_num 裝置例項路徑
|
398
520
|
# TD(Tramsmit Delay) = 15
|
@@ -1,10 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: moons_motor
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.5
|
4
4
|
Summary: This is a python library for controlling the Moons' motor through the serial port.
|
5
5
|
Author-email: miroc <mike8503111@gmail.com>
|
6
6
|
Project-URL: Repository, https://github.com/miroc99/moons_motor.git
|
7
|
-
Classifier: Development Status ::
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
8
8
|
Classifier: Intended Audience :: Developers
|
9
9
|
Classifier: Topic :: Terminals :: Serial
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -38,7 +38,7 @@ python -m pip install moons_motor
|
|
38
38
|
## Usage
|
39
39
|
|
40
40
|
```python
|
41
|
-
from motor import MoonsStepper, StepperModules
|
41
|
+
from motor import MoonsStepper, StepperModules, StepperCommand
|
42
42
|
import simulate
|
43
43
|
from time import sleep
|
44
44
|
|
@@ -47,9 +47,9 @@ motor = MoonsStepper(StepperModules.STM17S_3RN, "0403", "6001", "TESTA")
|
|
47
47
|
MoonsStepper.list_all_ports()
|
48
48
|
motor.connect()
|
49
49
|
|
50
|
-
motor.
|
50
|
+
motor.send_command(address="@", command=StepperCommand.JOG)
|
51
51
|
sleep(5)
|
52
|
-
motor.
|
52
|
+
motor.send_command(address="@", command=StepperCommand.STOP_JOG)
|
53
53
|
|
54
54
|
```
|
55
55
|
|
@@ -1,11 +1,11 @@
|
|
1
1
|
moons_motor/__init__.py,sha256=qOpsRwizV-DpKSvNzyvj8ju3cs6vwgIICur1Oe6sxOA,27
|
2
|
-
moons_motor/motor.py,sha256=
|
2
|
+
moons_motor/motor.py,sha256=LujQRwyndbeowiGcjvfPt_L8j3B2582hDJLYMD-8suM,17381
|
3
3
|
moons_motor/observer.py,sha256=PXzuPYKRb2HpjArJcD8HakYIPfFGAs1uBDIL8PSizgA,124
|
4
4
|
moons_motor/simulate.py,sha256=J0y1fZhoOim9i-BAkprxnPern1SAdkDfKPqT2MWyDwU,2561
|
5
5
|
moons_motor/status.py,sha256=jXQZFZTt9ugHktkWKLII8MpEQQaeO-UjlwTrrP4LJNE,2872
|
6
6
|
moons_motor/subject.py,sha256=L_GS6fvJTeX7X23o3T92oiZ4rtLVKA2OEd9GpHn_Dz4,445
|
7
|
-
moons_motor-0.1.
|
8
|
-
moons_motor-0.1.
|
9
|
-
moons_motor-0.1.
|
10
|
-
moons_motor-0.1.
|
11
|
-
moons_motor-0.1.
|
7
|
+
moons_motor-0.1.5.dist-info/licenses/LICENSE,sha256=nsYjO800SjIjI85y2kVHR5mC3tca2vs4kK_BhNe89bM,1074
|
8
|
+
moons_motor-0.1.5.dist-info/METADATA,sha256=enjd1RbUxi1c2mzwIAXenUEu-KegcywTe70SoIXiwQs,1403
|
9
|
+
moons_motor-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
+
moons_motor-0.1.5.dist-info/top_level.txt,sha256=0dE-CR5_NYBw34jHIDGQNWpMllzO6mtUIuKyRv_rJLg,12
|
11
|
+
moons_motor-0.1.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|